Skip to content

Conversation

@jacekradko
Copy link
Member

@jacekradko jacekradko commented Nov 24, 2025

Description

Fix race condition where multiple browser tabs could fetch session tokens simultaneously. The refreshTokenOnFocus handler now uses the same cross-tab lock as the session token poller, preventing duplicate API calls when switching between tabs or when focus events fire while another tab is already refreshing the token.

Checklist

  • pnpm test runs as expected.
  • pnpm build runs as expected.
  • (If applicable) JSDoc comments have been added or updated for any package exports
  • (If applicable) Documentation has been updated

Type of change

  • 🐛 Bug fix
  • 🌟 New feature
  • 🔨 Breaking change
  • 📖 Refactoring / dependency upgrade / documentation
  • other:

Summary by CodeRabbit

  • Bug Fixes

    • Fixed race conditions that could cause duplicate session token fetches across tabs, reducing unnecessary network calls and sign-in glitches.
    • Improved session polling reliability so retries are rescheduled even after errors.
  • New Features

    • Added a cross-tab locking utility to coordinate token fetches and avoid redundant requests.
    • Added ability to stop and restart session polling.
  • Tests

    • Added comprehensive tests for polling, locking, and token refresh sequencing.
  • Chores

    • Added a changeset for a patch release.

✏️ Tip: You can customize this high-level summary in your review settings.

@changeset-bot
Copy link

changeset-bot bot commented Nov 24, 2025

🦋 Changeset detected

Latest commit: 26f85df

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@clerk/clerk-js Patch
@clerk/chrome-extension Patch
@clerk/clerk-expo Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel
Copy link

vercel bot commented Nov 24, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
clerk-js-sandbox Ready Ready Preview Comment Dec 3, 2025 2:55am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 24, 2025

Walkthrough

Adds a changeset for a patch release; introduces a SafeLock API with timeouts and degraded fallback; removes cross-tab locking from SessionCookiePoller; adds per-token cross-tab locks in Session.ts to serialize token fetches; reorders cookie/devBrowser initialization in AuthCookieService; and adds tests for SafeLock and the poller. (50 words)

Changes

Cohort / File(s) Summary
Changeset
.changeset/fix-token-refresh-race-condition.md
New changeset declaring a patch release for @clerk/clerk-js noting a fix for multiple tabs fetching session tokens simultaneously.
SafeLock implementation
packages/clerk-js/src/core/auth/safeLock.ts
New/updated SafeLock exposing acquireLockAndRun(cb): Promise<T>; uses Web Locks API with AbortController and timeout in secure contexts, falls back to browser-tabs-lock in non-secure contexts, adds beforeunload cleanup, degraded-mode execution when lock cannot be acquired, and debug logging.
Per-token locking in Session
packages/clerk-js/src/core/resources/Session.ts
Adds per-tokenId SafeLock instances (tokenLocks map) and getTokenLock helper; implements fast-path cache check, double-checked locking with acquireLockAndRun, caches token resolvers to avoid duplicate requests, updates lastActiveToken, and emits token-related events.
SessionCookiePoller
packages/clerk-js/src/core/auth/SessionCookiePoller.ts
Removed internal SafeLock usage and related constants/imports; poller directly schedules and runs the refresh callback, with improved error handling, rescheduling in finally block, stopPollingForSessionToken() API, and an initiated guard to prevent re-entrant starts.
AuthCookieService adjustments
packages/clerk-js/src/core/auth/AuthCookieService.ts
Reordered and deferred initialization of cookie-related values and devBrowser construction arguments; moved sessionCookie initialization after devBrowser setup; aligned poller declaration/initialization order with token polling flow.
Tests
packages/clerk-js/src/core/auth/__tests__/SessionCookiePoller.test.ts, packages/clerk-js/src/core/auth/__tests__/safeLock.test.ts
New tests covering SessionCookiePoller lifecycle and polling timing (start/stop/restart and scheduling), and SafeLock behavior (Web Locks API path, shared-lock sequencing, degraded fallback, and API contract).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Areas needing careful review:
    • packages/clerk-js/src/core/resources/Session.ts: correctness of per-token lock map, double-checked locking, resolver caching, event emission, and handling of empty/unsigned tokens.
    • packages/clerk-js/src/core/auth/safeLock.ts: Web Locks API usage, AbortController timeout logic, fallback path correctness, and beforeunload cleanup safety.
    • packages/clerk-js/src/core/auth/SessionCookiePoller.ts: new start/stop semantics, error handling/rescheduling, and assumptions that cross-tab coordination occurs elsewhere.
    • packages/clerk-js/src/core/auth/AuthCookieService.ts: side effects from reordering initialization and devBrowser construction arguments.
    • Tests: ensure fake-timer usage and mocks correctly reflect runtime behavior and lock semantics.

Poem

🐰 I hopped through tabs where tokens would race,
Locks now hum softly to steady the pace.
One fetch at a time, no frantic ado,
Cookies and tokens lined up in queue.
A little rabbit clap — refreshes follow through.

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: fixing a race condition in token fetching across multiple browser tabs in clerk-js.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/token-poller-race-condition

Comment @coderabbitai help to get the list of available commands and usage tips.

@pkg-pr-new
Copy link

pkg-pr-new bot commented Nov 24, 2025

Open in StackBlitz

@clerk/agent-toolkit

npm i https://pkg.pr.new/@clerk/agent-toolkit@7304

@clerk/astro

npm i https://pkg.pr.new/@clerk/astro@7304

@clerk/backend

npm i https://pkg.pr.new/@clerk/backend@7304

@clerk/chrome-extension

npm i https://pkg.pr.new/@clerk/chrome-extension@7304

@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@7304

@clerk/dev-cli

npm i https://pkg.pr.new/@clerk/dev-cli@7304

@clerk/elements

npm i https://pkg.pr.new/@clerk/elements@7304

@clerk/clerk-expo

npm i https://pkg.pr.new/@clerk/clerk-expo@7304

@clerk/expo-passkeys

npm i https://pkg.pr.new/@clerk/expo-passkeys@7304

@clerk/express

npm i https://pkg.pr.new/@clerk/express@7304

@clerk/fastify

npm i https://pkg.pr.new/@clerk/fastify@7304

@clerk/localizations

npm i https://pkg.pr.new/@clerk/localizations@7304

@clerk/nextjs

npm i https://pkg.pr.new/@clerk/nextjs@7304

@clerk/nuxt

npm i https://pkg.pr.new/@clerk/nuxt@7304

@clerk/clerk-react

npm i https://pkg.pr.new/@clerk/clerk-react@7304

@clerk/react-router

npm i https://pkg.pr.new/@clerk/react-router@7304

@clerk/remix

npm i https://pkg.pr.new/@clerk/remix@7304

@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@7304

@clerk/tanstack-react-start

npm i https://pkg.pr.new/@clerk/tanstack-react-start@7304

@clerk/testing

npm i https://pkg.pr.new/@clerk/testing@7304

@clerk/themes

npm i https://pkg.pr.new/@clerk/themes@7304

@clerk/types

npm i https://pkg.pr.new/@clerk/types@7304

@clerk/upgrade

npm i https://pkg.pr.new/@clerk/upgrade@7304

@clerk/vue

npm i https://pkg.pr.new/@clerk/vue@7304

commit: 26f85df

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/clerk-js/src/core/auth/safeLock.ts (1)

16-38: Fix inconsistent return type behavior.

The acquireLockAndRun function has inconsistent return behavior:

  • Navigator.locks path (line 25-28): Returns false on error via .catch()
  • Fallback path (line 31-37): Returns undefined implicitly when lock acquisition fails (after line 37)

This creates ambiguity for callers: should they check for false, undefined, or both to detect failure?

Consider one of these approaches:

Option 1 (Recommended): Return a consistent sentinel value

   const acquireLockAndRun = async (cb: () => Promise<unknown>) => {
     if ('locks' in navigator && isSecureContext) {
       const controller = new AbortController();
       const lockTimeout = setTimeout(() => controller.abort(), 4999);
       return await navigator.locks
         .request(key, { signal: controller.signal }, async () => {
           clearTimeout(lockTimeout);
           return await cb();
         })
         .catch(() => {
           // browser-tabs-lock never seems to throw, so we are mirroring the behavior here
           return false;
         });
     }
 
     if (await lock.acquireLock(key, 5000)) {
       try {
         return await cb();
       } finally {
         await lock.releaseLock(key);
       }
     }
+    
+    // Return false when lock acquisition fails to match navigator.locks behavior
+    return false;
   };

Option 2: Use a Result/Option type for clearer semantics

This would make success/failure explicit but requires more extensive changes.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between cfaa021 and c694bae.

📒 Files selected for processing (4)
  • .changeset/fix-token-refresh-race-condition.md (1 hunks)
  • packages/clerk-js/src/core/auth/AuthCookieService.ts (6 hunks)
  • packages/clerk-js/src/core/auth/SessionCookiePoller.ts (1 hunks)
  • packages/clerk-js/src/core/auth/safeLock.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

All code must pass ESLint checks with the project's configuration

Files:

  • packages/clerk-js/src/core/auth/safeLock.ts
  • packages/clerk-js/src/core/auth/AuthCookieService.ts
  • packages/clerk-js/src/core/auth/SessionCookiePoller.ts
**/*.{js,jsx,ts,tsx,json,md,yml,yaml}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Use Prettier for consistent code formatting

Files:

  • packages/clerk-js/src/core/auth/safeLock.ts
  • packages/clerk-js/src/core/auth/AuthCookieService.ts
  • packages/clerk-js/src/core/auth/SessionCookiePoller.ts
packages/**/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

TypeScript is required for all packages

Files:

  • packages/clerk-js/src/core/auth/safeLock.ts
  • packages/clerk-js/src/core/auth/AuthCookieService.ts
  • packages/clerk-js/src/core/auth/SessionCookiePoller.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Follow established naming conventions (PascalCase for components, camelCase for variables)

Prefer importing types from @clerk/shared/types instead of the deprecated @clerk/types alias

Files:

  • packages/clerk-js/src/core/auth/safeLock.ts
  • packages/clerk-js/src/core/auth/AuthCookieService.ts
  • packages/clerk-js/src/core/auth/SessionCookiePoller.ts
packages/**/src/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

packages/**/src/**/*.{ts,tsx,js,jsx}: Maintain comprehensive JSDoc comments for public APIs
Use tree-shaking friendly exports
Validate all inputs and sanitize outputs
All public APIs must be documented with JSDoc
Use dynamic imports for optional features
Provide meaningful error messages to developers
Include error recovery suggestions where applicable
Log errors appropriately for debugging
Lazy load components and features when possible
Implement proper caching strategies
Use efficient data structures and algorithms
Implement proper logging with different levels

Files:

  • packages/clerk-js/src/core/auth/safeLock.ts
  • packages/clerk-js/src/core/auth/AuthCookieService.ts
  • packages/clerk-js/src/core/auth/SessionCookiePoller.ts
**/*.ts?(x)

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Use proper TypeScript error types

Files:

  • packages/clerk-js/src/core/auth/safeLock.ts
  • packages/clerk-js/src/core/auth/AuthCookieService.ts
  • packages/clerk-js/src/core/auth/SessionCookiePoller.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/typescript.mdc)

**/*.{ts,tsx}: Always define explicit return types for functions, especially public APIs
Use proper type annotations for variables and parameters where inference isn't clear
Avoid any type - prefer unknown when type is uncertain, then narrow with type guards
Implement type guards for unknown types using the pattern function isType(value: unknown): value is Type
Use interface for object shapes that might be extended
Use type for unions, primitives, and computed types
Prefer readonly properties for immutable data structures
Use private for internal implementation details in classes
Use protected for inheritance hierarchies
Use public explicitly for clarity in public APIs
Use mixins for shared behavior across unrelated classes in TypeScript
Use generic constraints with bounded type parameters like <T extends { id: string }>
Use utility types like Omit, Partial, and Pick for data transformation instead of manual type construction
Use discriminated unions instead of boolean flags for state management and API responses
Use mapped types for transforming object types
Use conditional types for type-level logic
Leverage template literal types for string manipulation at the type level
Use ES6 imports/exports consistently
Use default exports sparingly, prefer named exports
Document functions with JSDoc comments including @param, @returns, @throws, and @example tags
Create custom error classes that extend Error for specific error types
Use the Result pattern for error handling instead of throwing exceptions
Use optional chaining (?.) and nullish coalescing (??) operators for safe property access
Let TypeScript infer obvious types to reduce verbosity
Use const assertions with as const for literal types
Use satisfies operator for type checking without widening types
Declare readonly arrays and objects for immutable data structures
Use spread operator and array spread for immutable updates instead of mutations
Use lazy loading for large types...

Files:

  • packages/clerk-js/src/core/auth/safeLock.ts
  • packages/clerk-js/src/core/auth/AuthCookieService.ts
  • packages/clerk-js/src/core/auth/SessionCookiePoller.ts
🧬 Code graph analysis (2)
packages/clerk-js/src/core/auth/AuthCookieService.ts (4)
packages/clerk-js/src/core/auth/cookies/clientUat.ts (2)
  • ClientUatCookieHandler (12-15)
  • createClientUatCookie (23-65)
packages/clerk-js/src/core/auth/SessionCookiePoller.ts (2)
  • SessionCookiePoller (9-42)
  • REFRESH_SESSION_TOKEN_LOCK_KEY (6-6)
packages/clerk-js/src/core/auth/cookies/session.ts (2)
  • SessionCookieHandler (10-14)
  • createSessionCookie (28-59)
packages/clerk-js/src/core/auth/safeLock.ts (2)
  • SafeLockReturn (3-5)
  • SafeLock (7-41)
packages/clerk-js/src/core/auth/SessionCookiePoller.ts (1)
packages/clerk-js/src/core/auth/safeLock.ts (2)
  • SafeLockReturn (3-5)
  • SafeLock (7-41)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Formatting | Dedupe | Changeset
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: semgrep-cloud-platform/scan
  • GitHub Check: semgrep-cloud-platform/scan
🔇 Additional comments (5)
packages/clerk-js/src/core/auth/SessionCookiePoller.ts (1)

6-6: Good: Exporting the lock key enables cross-component coordination.

Exporting REFRESH_SESSION_TOKEN_LOCK_KEY allows AuthCookieService to create a shared lock using the same key, which is essential for the cross-tab synchronization mechanism.

packages/clerk-js/src/core/auth/AuthCookieService.ts (4)

72-75: Excellent: Clear documentation of the locking mechanism.

The comment clearly explains the purpose of the tokenRefreshLock and how it coordinates cross-tab token refresh. This helps future maintainers understand the synchronization strategy.


137-137: Good: Shared lock injected into poller.

Passing this.tokenRefreshLock to the SessionCookiePoller constructor ensures both the poller and the focus handler use the same cross-tab lock, which is essential for preventing the race condition described in the PR objectives.


158-162: Good: Focus handler now uses the shared lock.

Wrapping the refreshSessionToken call in this.tokenRefreshLock.acquireLockAndRun() prevents concurrent token fetches when multiple tabs gain focus simultaneously or when focus events fire while the poller is already refreshing.

The updated comment clearly explains the cross-tab coordination mechanism.


47-47: Properties are properly initialized in the constructor — no issue exists.

The properties clientUat and sessionCookie are declared without explicit initialization on lines 47 and 50, but they are both assigned values in the constructor (lines ~88–95 via createClientUatCookie() and createSessionCookie()). TypeScript's strictPropertyInitialization rule only flags properties that are declared without initialization AND never assigned in the constructor. Since these properties are assigned before any use, they comply with strict mode requirements and require no changes.

Likely an incorrect or invalid review comment.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (5)
packages/clerk-js/src/core/auth/safeLock.ts (2)

42-46: Memory leak: event listener is never removed.

Each call to SafeLock() adds a new beforeunload listener that is never cleaned up. If SafeLock is called multiple times (e.g., during hot reloads in development), listeners accumulate.

Consider returning a cleanup function or ensuring SafeLock is only instantiated once per key:

 export function SafeLock(key: string): SafeLockReturn {
   const lock = new Lock();

+  const handleBeforeUnload = async () => {
+    await lock.releaseLock(key);
+  };
+
   // Release any held locks when the tab is closing to prevent deadlocks
   // eslint-disable-next-line @typescript-eslint/no-misused-promises
-  window.addEventListener('beforeunload', async () => {
-    await lock.releaseLock(key);
-  });
+  window.addEventListener('beforeunload', handleBeforeUnload);

Since this is scoped to single-instance usage (via AuthCookieService), this is low-risk but worth noting for future reuse.


48-74: Consider adding explicit return type annotation for type safety.

Per coding guidelines, explicit return types should be defined for functions. While TypeScript can infer the return type, an explicit annotation would improve clarity and catch type mismatches earlier.

-  const acquireLockAndRun = async (cb: () => Promise<unknown>) => {
+  const acquireLockAndRun = async (cb: () => Promise<unknown>): Promise<unknown> => {
packages/clerk-js/src/core/auth/__tests__/SessionCookiePoller.test.ts (2)

82-96: Consider testing lock acquisition failure scenario.

The tests cover the happy path well, but there's no test for when acquireLockAndRun returns false (timeout/failure). This edge case is part of the SafeLockReturn contract.

it('handles lock acquisition timeout gracefully', async () => {
  const sharedLock: SafeLockReturn = {
    acquireLockAndRun: vi.fn().mockResolvedValue(false), // Lock timeout
  };

  const poller = new SessionCookiePoller(sharedLock);
  const callback = vi.fn().mockResolvedValue(undefined);

  poller.startPollingForSessionToken(callback);

  // Callback should not be invoked when lock returns false
  expect(callback).not.toHaveBeenCalled();

  poller.stopPollingForSessionToken();
});

100-121: Unnecessary async on test function.

The test doesn't use await at the top level. While not harmful, removing async would be cleaner.

-    it('allows restart after stop', async () => {
+    it('allows restart after stop', () => {
packages/clerk-js/src/core/auth/__tests__/safeLock.test.ts (1)

83-104: Test name is misleading - mock doesn't actually simulate sequential execution.

The test "mock lock can simulate sequential execution" fires both promises concurrently via Promise.all. The mock implementation executes callbacks in parallel, not sequentially as a real lock would. This doesn't validate actual sequential behavior.

Consider either:

  1. Renaming to clarify it tests concurrent invocation tracking
  2. Or implementing actual sequential simulation with a queue
-    it('mock lock can simulate sequential execution', async () => {
+    it('tracks multiple concurrent lock invocations', async () => {
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between c694bae and b991f29.

📒 Files selected for processing (5)
  • packages/clerk-js/src/core/auth/AuthCookieService.ts (6 hunks)
  • packages/clerk-js/src/core/auth/SessionCookiePoller.ts (1 hunks)
  • packages/clerk-js/src/core/auth/__tests__/SessionCookiePoller.test.ts (1 hunks)
  • packages/clerk-js/src/core/auth/__tests__/safeLock.test.ts (1 hunks)
  • packages/clerk-js/src/core/auth/safeLock.ts (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/clerk-js/src/core/auth/SessionCookiePoller.ts
🧰 Additional context used
📓 Path-based instructions (9)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

All code must pass ESLint checks with the project's configuration

Files:

  • packages/clerk-js/src/core/auth/__tests__/SessionCookiePoller.test.ts
  • packages/clerk-js/src/core/auth/__tests__/safeLock.test.ts
  • packages/clerk-js/src/core/auth/AuthCookieService.ts
  • packages/clerk-js/src/core/auth/safeLock.ts
**/*.{js,jsx,ts,tsx,json,md,yml,yaml}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Use Prettier for consistent code formatting

Files:

  • packages/clerk-js/src/core/auth/__tests__/SessionCookiePoller.test.ts
  • packages/clerk-js/src/core/auth/__tests__/safeLock.test.ts
  • packages/clerk-js/src/core/auth/AuthCookieService.ts
  • packages/clerk-js/src/core/auth/safeLock.ts
packages/**/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

TypeScript is required for all packages

Files:

  • packages/clerk-js/src/core/auth/__tests__/SessionCookiePoller.test.ts
  • packages/clerk-js/src/core/auth/__tests__/safeLock.test.ts
  • packages/clerk-js/src/core/auth/AuthCookieService.ts
  • packages/clerk-js/src/core/auth/safeLock.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Follow established naming conventions (PascalCase for components, camelCase for variables)

Prefer importing types from @clerk/shared/types instead of the deprecated @clerk/types alias

Files:

  • packages/clerk-js/src/core/auth/__tests__/SessionCookiePoller.test.ts
  • packages/clerk-js/src/core/auth/__tests__/safeLock.test.ts
  • packages/clerk-js/src/core/auth/AuthCookieService.ts
  • packages/clerk-js/src/core/auth/safeLock.ts
packages/**/src/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

packages/**/src/**/*.{ts,tsx,js,jsx}: Maintain comprehensive JSDoc comments for public APIs
Use tree-shaking friendly exports
Validate all inputs and sanitize outputs
All public APIs must be documented with JSDoc
Use dynamic imports for optional features
Provide meaningful error messages to developers
Include error recovery suggestions where applicable
Log errors appropriately for debugging
Lazy load components and features when possible
Implement proper caching strategies
Use efficient data structures and algorithms
Implement proper logging with different levels

Files:

  • packages/clerk-js/src/core/auth/__tests__/SessionCookiePoller.test.ts
  • packages/clerk-js/src/core/auth/__tests__/safeLock.test.ts
  • packages/clerk-js/src/core/auth/AuthCookieService.ts
  • packages/clerk-js/src/core/auth/safeLock.ts
**/*.{test,spec}.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

**/*.{test,spec}.{ts,tsx,js,jsx}: Unit tests are required for all new functionality
Verify proper error handling and edge cases
Include tests for all new features

Files:

  • packages/clerk-js/src/core/auth/__tests__/SessionCookiePoller.test.ts
  • packages/clerk-js/src/core/auth/__tests__/safeLock.test.ts
**/*.ts?(x)

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Use proper TypeScript error types

Files:

  • packages/clerk-js/src/core/auth/__tests__/SessionCookiePoller.test.ts
  • packages/clerk-js/src/core/auth/__tests__/safeLock.test.ts
  • packages/clerk-js/src/core/auth/AuthCookieService.ts
  • packages/clerk-js/src/core/auth/safeLock.ts
**/*.{test,spec,e2e}.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Use real Clerk instances for integration tests

Files:

  • packages/clerk-js/src/core/auth/__tests__/SessionCookiePoller.test.ts
  • packages/clerk-js/src/core/auth/__tests__/safeLock.test.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/typescript.mdc)

**/*.{ts,tsx}: Always define explicit return types for functions, especially public APIs
Use proper type annotations for variables and parameters where inference isn't clear
Avoid any type - prefer unknown when type is uncertain, then narrow with type guards
Implement type guards for unknown types using the pattern function isType(value: unknown): value is Type
Use interface for object shapes that might be extended
Use type for unions, primitives, and computed types
Prefer readonly properties for immutable data structures
Use private for internal implementation details in classes
Use protected for inheritance hierarchies
Use public explicitly for clarity in public APIs
Use mixins for shared behavior across unrelated classes in TypeScript
Use generic constraints with bounded type parameters like <T extends { id: string }>
Use utility types like Omit, Partial, and Pick for data transformation instead of manual type construction
Use discriminated unions instead of boolean flags for state management and API responses
Use mapped types for transforming object types
Use conditional types for type-level logic
Leverage template literal types for string manipulation at the type level
Use ES6 imports/exports consistently
Use default exports sparingly, prefer named exports
Document functions with JSDoc comments including @param, @returns, @throws, and @example tags
Create custom error classes that extend Error for specific error types
Use the Result pattern for error handling instead of throwing exceptions
Use optional chaining (?.) and nullish coalescing (??) operators for safe property access
Let TypeScript infer obvious types to reduce verbosity
Use const assertions with as const for literal types
Use satisfies operator for type checking without widening types
Declare readonly arrays and objects for immutable data structures
Use spread operator and array spread for immutable updates instead of mutations
Use lazy loading for large types...

Files:

  • packages/clerk-js/src/core/auth/__tests__/SessionCookiePoller.test.ts
  • packages/clerk-js/src/core/auth/__tests__/safeLock.test.ts
  • packages/clerk-js/src/core/auth/AuthCookieService.ts
  • packages/clerk-js/src/core/auth/safeLock.ts
🧬 Code graph analysis (2)
packages/clerk-js/src/core/auth/__tests__/SessionCookiePoller.test.ts (2)
packages/clerk-js/src/core/auth/safeLock.ts (1)
  • SafeLockReturn (6-15)
packages/clerk-js/src/core/auth/SessionCookiePoller.ts (1)
  • SessionCookiePoller (27-60)
packages/clerk-js/src/core/auth/__tests__/safeLock.test.ts (1)
packages/clerk-js/src/core/auth/safeLock.ts (2)
  • SafeLock (39-77)
  • SafeLockReturn (6-15)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (34)
  • GitHub Check: Integration Tests (nextjs, chrome, 15, RQ)
  • GitHub Check: Integration Tests (quickstart, chrome, 16)
  • GitHub Check: Integration Tests (billing, chrome, RQ)
  • GitHub Check: Integration Tests (nextjs, chrome, 16)
  • GitHub Check: Integration Tests (machine, chrome)
  • GitHub Check: Integration Tests (localhost, chrome)
  • GitHub Check: Integration Tests (quickstart, chrome, 15)
  • GitHub Check: Integration Tests (vue, chrome)
  • GitHub Check: Integration Tests (machine, chrome, RQ)
  • GitHub Check: Integration Tests (react-router, chrome)
  • GitHub Check: Integration Tests (nextjs, chrome, 14)
  • GitHub Check: Integration Tests (expo-web, chrome)
  • GitHub Check: Integration Tests (astro, chrome)
  • GitHub Check: Integration Tests (tanstack-react-start, chrome)
  • GitHub Check: Integration Tests (custom, chrome)
  • GitHub Check: Integration Tests (nextjs, chrome, 15)
  • GitHub Check: Integration Tests (sessions:staging, chrome)
  • GitHub Check: Integration Tests (nuxt, chrome)
  • GitHub Check: Integration Tests (handshake, chrome)
  • GitHub Check: Integration Tests (handshake:staging, chrome)
  • GitHub Check: Integration Tests (ap-flows, chrome)
  • GitHub Check: Integration Tests (elements, chrome)
  • GitHub Check: Integration Tests (billing, chrome)
  • GitHub Check: Integration Tests (sessions, chrome)
  • GitHub Check: Integration Tests (express, chrome)
  • GitHub Check: Integration Tests (generic, chrome)
  • GitHub Check: Publish with pkg-pr-new
  • GitHub Check: Unit Tests (22, shared, clerk-js, RQ)
  • GitHub Check: Unit Tests (22, **)
  • GitHub Check: Static analysis
  • GitHub Check: Formatting | Dedupe | Changeset
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: semgrep-cloud-platform/scan
  • GitHub Check: semgrep-cloud-platform/scan
🔇 Additional comments (11)
packages/clerk-js/src/core/auth/safeLock.ts (2)

3-15: JSDoc documentation added - addresses past review feedback.

The interface now has proper JSDoc documentation explaining the cross-tab lock coordination purpose and the acquireLockAndRun method behavior including the timeout return value.


17-39: JSDoc documentation added - addresses past review feedback.

The function now has comprehensive JSDoc including purpose, parameters, return type, and a practical example demonstrating cross-tab coordination.

packages/clerk-js/src/core/auth/__tests__/SessionCookiePoller.test.ts (2)

6-14: LGTM - Clean test setup.

Proper use of beforeEach/afterEach for fake timer management and mock restoration.


16-64: Good coverage for shared lock coordination pattern.

These tests effectively validate the core fix - that an external lock can be injected and shared between the poller and focus handler, which is the key mechanism for preventing the race condition.

packages/clerk-js/src/core/auth/__tests__/safeLock.test.ts (2)

6-23: LGTM - Good interface contract tests.

These tests validate the API surface and mockability of SafeLockReturn, which is important for the dependency injection pattern used in SessionCookiePoller.


25-45: Good defensive testing with environment check.

The conditional skip for environments without Web Locks API is appropriate. The test validates the callback is invoked and the result propagates correctly.

packages/clerk-js/src/core/auth/AuthCookieService.ts (5)

23-25: LGTM - Clean imports for the new lock coordination feature.

Proper separation of type import (SafeLockReturn) from value imports (SafeLock, REFRESH_SESSION_TOKEN_LOCK_KEY).


51-54: Good documentation for the shared lock member.

The JSDoc clearly explains the purpose of tokenRefreshLock for cross-tab coordination.


75-78: Lock creation is appropriately positioned.

Creating the lock early in the constructor ensures it's available before refreshTokenOnFocus() and startPollingForToken() are called.


138-142: Correctly passes shared lock to poller.

The poller now receives the same tokenRefreshLock that the focus handler uses, enabling cross-tab coordination.


152-168: Core fix: Focus handler now uses shared lock - LGTM.

This is the key change that fixes the race condition. By wrapping refreshSessionToken in tokenRefreshLock.acquireLockAndRun(), the focus handler will coordinate with both the poller and other tabs, preventing duplicate API calls.

The comment clearly explains the coordination mechanism.

@jacekradko jacekradko requested a review from brkalow November 25, 2025 03:07
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
packages/clerk-js/src/core/resources/Session.ts (1)

45-64: Potential memory leak: tokenLocks Map grows unboundedly.

The tokenLocks Map accumulates entries for each unique tokenId (session + org + template combination) but never removes them. Over a long-lived session where users switch organizations or use different JWT templates, this could lead to memory growth.

Consider adding a cleanup mechanism, such as:

  • Using a WeakMap if the key could be an object
  • Adding a maximum size with LRU eviction
  • Clearing entries when the session ends

For now, this is low risk since each entry is lightweight (just a SafeLock instance), but worth noting for long-term maintenance.

packages/clerk-js/src/core/auth/safeLock.ts (1)

18-22: Multiple beforeunload listeners accumulate with each SafeLock instance.

Since SafeLock is called once per unique tokenId (via getTokenLock in Session.ts), a new beforeunload listener is registered for each lock instance. Over time with many different token types, this accumulates listeners.

Consider:

  1. Moving the listener registration outside the function (module-level, once)
  2. Tracking all lock instances in a module-level registry and releasing them in a single listener
  3. Using { once: true } isn't suitable here since we need it to fire every time
+const lockRegistry = new Map<string, Lock>();
+
+// Single listener to release all locks on tab close
+window.addEventListener('beforeunload', async () => {
+  for (const [key, lock] of lockRegistry) {
+    await lock.releaseLock(key);
+  }
+});
+
 export function SafeLock(key: string) {
   const lock = new Lock();
-
-  // Release any held locks when the tab is closing to prevent deadlocks
-  // eslint-disable-next-line @typescript-eslint/no-misused-promises
-  window.addEventListener('beforeunload', async () => {
-    await lock.releaseLock(key);
-  });
+  lockRegistry.set(key, lock);
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between b991f29 and ca20b97.

📒 Files selected for processing (5)
  • .changeset/fix-token-refresh-race-condition.md (1 hunks)
  • packages/clerk-js/src/core/auth/SessionCookiePoller.ts (2 hunks)
  • packages/clerk-js/src/core/auth/__tests__/SessionCookiePoller.test.ts (1 hunks)
  • packages/clerk-js/src/core/auth/safeLock.ts (1 hunks)
  • packages/clerk-js/src/core/resources/Session.ts (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/clerk-js/src/core/auth/SessionCookiePoller.ts
🧰 Additional context used
📓 Path-based instructions (9)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

All code must pass ESLint checks with the project's configuration

Files:

  • packages/clerk-js/src/core/resources/Session.ts
  • packages/clerk-js/src/core/auth/__tests__/SessionCookiePoller.test.ts
  • packages/clerk-js/src/core/auth/safeLock.ts
**/*.{js,jsx,ts,tsx,json,md,yml,yaml}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Use Prettier for consistent code formatting

Files:

  • packages/clerk-js/src/core/resources/Session.ts
  • packages/clerk-js/src/core/auth/__tests__/SessionCookiePoller.test.ts
  • packages/clerk-js/src/core/auth/safeLock.ts
packages/**/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

TypeScript is required for all packages

Files:

  • packages/clerk-js/src/core/resources/Session.ts
  • packages/clerk-js/src/core/auth/__tests__/SessionCookiePoller.test.ts
  • packages/clerk-js/src/core/auth/safeLock.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Follow established naming conventions (PascalCase for components, camelCase for variables)

Prefer importing types from @clerk/shared/types instead of the deprecated @clerk/types alias

Files:

  • packages/clerk-js/src/core/resources/Session.ts
  • packages/clerk-js/src/core/auth/__tests__/SessionCookiePoller.test.ts
  • packages/clerk-js/src/core/auth/safeLock.ts
packages/**/src/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

packages/**/src/**/*.{ts,tsx,js,jsx}: Maintain comprehensive JSDoc comments for public APIs
Use tree-shaking friendly exports
Validate all inputs and sanitize outputs
All public APIs must be documented with JSDoc
Use dynamic imports for optional features
Provide meaningful error messages to developers
Include error recovery suggestions where applicable
Log errors appropriately for debugging
Lazy load components and features when possible
Implement proper caching strategies
Use efficient data structures and algorithms
Implement proper logging with different levels

Files:

  • packages/clerk-js/src/core/resources/Session.ts
  • packages/clerk-js/src/core/auth/__tests__/SessionCookiePoller.test.ts
  • packages/clerk-js/src/core/auth/safeLock.ts
**/*.ts?(x)

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Use proper TypeScript error types

Files:

  • packages/clerk-js/src/core/resources/Session.ts
  • packages/clerk-js/src/core/auth/__tests__/SessionCookiePoller.test.ts
  • packages/clerk-js/src/core/auth/safeLock.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/typescript.mdc)

**/*.{ts,tsx}: Always define explicit return types for functions, especially public APIs
Use proper type annotations for variables and parameters where inference isn't clear
Avoid any type - prefer unknown when type is uncertain, then narrow with type guards
Implement type guards for unknown types using the pattern function isType(value: unknown): value is Type
Use interface for object shapes that might be extended
Use type for unions, primitives, and computed types
Prefer readonly properties for immutable data structures
Use private for internal implementation details in classes
Use protected for inheritance hierarchies
Use public explicitly for clarity in public APIs
Use mixins for shared behavior across unrelated classes in TypeScript
Use generic constraints with bounded type parameters like <T extends { id: string }>
Use utility types like Omit, Partial, and Pick for data transformation instead of manual type construction
Use discriminated unions instead of boolean flags for state management and API responses
Use mapped types for transforming object types
Use conditional types for type-level logic
Leverage template literal types for string manipulation at the type level
Use ES6 imports/exports consistently
Use default exports sparingly, prefer named exports
Document functions with JSDoc comments including @param, @returns, @throws, and @example tags
Create custom error classes that extend Error for specific error types
Use the Result pattern for error handling instead of throwing exceptions
Use optional chaining (?.) and nullish coalescing (??) operators for safe property access
Let TypeScript infer obvious types to reduce verbosity
Use const assertions with as const for literal types
Use satisfies operator for type checking without widening types
Declare readonly arrays and objects for immutable data structures
Use spread operator and array spread for immutable updates instead of mutations
Use lazy loading for large types...

Files:

  • packages/clerk-js/src/core/resources/Session.ts
  • packages/clerk-js/src/core/auth/__tests__/SessionCookiePoller.test.ts
  • packages/clerk-js/src/core/auth/safeLock.ts
**/*.{test,spec}.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

**/*.{test,spec}.{ts,tsx,js,jsx}: Unit tests are required for all new functionality
Verify proper error handling and edge cases
Include tests for all new features

Files:

  • packages/clerk-js/src/core/auth/__tests__/SessionCookiePoller.test.ts
**/*.{test,spec,e2e}.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Use real Clerk instances for integration tests

Files:

  • packages/clerk-js/src/core/auth/__tests__/SessionCookiePoller.test.ts
🧬 Code graph analysis (2)
packages/clerk-js/src/core/auth/__tests__/SessionCookiePoller.test.ts (1)
packages/clerk-js/src/core/auth/SessionCookiePoller.ts (1)
  • SessionCookiePoller (11-39)
packages/clerk-js/src/core/auth/safeLock.ts (1)
packages/clerk-js/src/utils/debug.ts (1)
  • debugLogger (150-179)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Formatting | Dedupe | Changeset
  • GitHub Check: Build Packages
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: semgrep-cloud-platform/scan
  • GitHub Check: semgrep-cloud-platform/scan
🔇 Additional comments (4)
.changeset/fix-token-refresh-race-condition.md (1)

1-14: LGTM!

The changeset provides clear and comprehensive documentation of the race condition fix, including the key implementation details: cross-tab locking, per-tokenId coordination, double-checked locking pattern, and graceful timeout handling.

packages/clerk-js/src/core/auth/__tests__/SessionCookiePoller.test.ts (1)

1-142: LGTM!

Comprehensive test coverage for SessionCookiePoller with well-structured tests covering:

  • Immediate callback execution on start
  • Prevention of concurrent polling sessions
  • Proper stop and restart behavior
  • Correct scheduling that waits for callback completion

The use of fake timers and proper cleanup in each test is appropriate.

packages/clerk-js/src/core/resources/Session.ts (1)

416-473: Well-implemented double-checked locking pattern.

The implementation correctly:

  1. Checks cache before acquiring lock (fast path)
  2. Re-checks cache after acquiring lock (prevents duplicate fetches when another tab populated it)
  3. Caches the promise immediately to prevent concurrent in-tab duplicates
  4. Holds the lock until the token is resolved and cached
packages/clerk-js/src/core/auth/safeLock.ts (1)

29-59: Solid lock acquisition implementation with appropriate degraded mode handling.

The implementation correctly:

  • Uses Web Locks API in secure contexts with abort-based timeout
  • Falls back to localStorage-based locking in non-secure contexts
  • Ensures operations complete in degraded mode rather than failing silently
  • Properly releases locks in the fallback path via finally block

The generic return type <T> preserves type safety for callers.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
packages/clerk-js/src/core/resources/Session.ts (1)

38-64: Per-token SafeLock map looks good; consider lifecycle/typing polish

The per-tokenId SafeLock registry is a reasonable way to allow parallelism across token types while preventing duplicate fetches for the same token. One minor consideration: tokenLocks is module-global and never pruned, so in very long-lived apps with many distinct tokenIds this Map could grow without bound. In practice tokenId cardinality is likely low, but if this ever becomes a concern, you could consider:

  • Clearing entries when a session is ended/removed, or
  • Introducing a small LRU or size cap.

You might also consider giving getTokenLock an explicit return type (ReturnType<typeof SafeLock>) and extracting that into a type TokenLock = ReturnType<typeof SafeLock>; alias to make the intent clearer.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 04b08a6 and f94b214.

📒 Files selected for processing (1)
  • packages/clerk-js/src/core/resources/Session.ts (3 hunks)
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

All code must pass ESLint checks with the project's configuration

Files:

  • packages/clerk-js/src/core/resources/Session.ts
**/*.{js,jsx,ts,tsx,json,md,yml,yaml}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Use Prettier for consistent code formatting

Files:

  • packages/clerk-js/src/core/resources/Session.ts
packages/**/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

TypeScript is required for all packages

Files:

  • packages/clerk-js/src/core/resources/Session.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Follow established naming conventions (PascalCase for components, camelCase for variables)

Prefer importing types from @clerk/shared/types instead of the deprecated @clerk/types alias

Files:

  • packages/clerk-js/src/core/resources/Session.ts
packages/**/src/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

packages/**/src/**/*.{ts,tsx,js,jsx}: Maintain comprehensive JSDoc comments for public APIs
Use tree-shaking friendly exports
Validate all inputs and sanitize outputs
All public APIs must be documented with JSDoc
Use dynamic imports for optional features
Provide meaningful error messages to developers
Include error recovery suggestions where applicable
Log errors appropriately for debugging
Lazy load components and features when possible
Implement proper caching strategies
Use efficient data structures and algorithms
Implement proper logging with different levels

Files:

  • packages/clerk-js/src/core/resources/Session.ts
**/*.ts?(x)

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Use proper TypeScript error types

Files:

  • packages/clerk-js/src/core/resources/Session.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/typescript.mdc)

**/*.{ts,tsx}: Always define explicit return types for functions, especially public APIs
Use proper type annotations for variables and parameters where inference isn't clear
Avoid any type - prefer unknown when type is uncertain, then narrow with type guards
Implement type guards for unknown types using the pattern function isType(value: unknown): value is Type
Use interface for object shapes that might be extended
Use type for unions, primitives, and computed types
Prefer readonly properties for immutable data structures
Use private for internal implementation details in classes
Use protected for inheritance hierarchies
Use public explicitly for clarity in public APIs
Use mixins for shared behavior across unrelated classes in TypeScript
Use generic constraints with bounded type parameters like <T extends { id: string }>
Use utility types like Omit, Partial, and Pick for data transformation instead of manual type construction
Use discriminated unions instead of boolean flags for state management and API responses
Use mapped types for transforming object types
Use conditional types for type-level logic
Leverage template literal types for string manipulation at the type level
Use ES6 imports/exports consistently
Use default exports sparingly, prefer named exports
Document functions with JSDoc comments including @param, @returns, @throws, and @example tags
Create custom error classes that extend Error for specific error types
Use the Result pattern for error handling instead of throwing exceptions
Use optional chaining (?.) and nullish coalescing (??) operators for safe property access
Let TypeScript infer obvious types to reduce verbosity
Use const assertions with as const for literal types
Use satisfies operator for type checking without widening types
Declare readonly arrays and objects for immutable data structures
Use spread operator and array spread for immutable updates instead of mutations
Use lazy loading for large types...

Files:

  • packages/clerk-js/src/core/resources/Session.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Formatting | Dedupe | Changeset
  • GitHub Check: Build Packages
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: semgrep-cloud-platform/scan
  • GitHub Check: semgrep-cloud-platform/scan
🔇 Additional comments (2)
packages/clerk-js/src/core/resources/Session.ts (2)

394-407: Fast-path cache read before acquiring the lock is correct

The “check cache, then lock” flow looks solid: you avoid unnecessary lock acquisition on hot cache hits, preserve the existing TokenUpdate emission for cached tokens, and still normalize empty raw strings to null. This aligns well with the new cross-tab locking behavior without adding contention to the common case.


409-465: skipCache semantics are intentional and working correctly—no action needed

Verification confirms the behavior matches the review's description and is intentional:

  1. Behavior verified: When skipCache=true, the code explicitly bypasses both pre-lock and post-lock cache reads (lines 395, 415: skipCache ? undefined : SessionTokenCache.get()), while still caching the result (line 450). Concurrent skipCache=true calls for the same tokenId will each trigger API requests rather than deduping in-flight promises.

  2. Intent is explicit: The conditional checks at lines 395 and 415 confirm this is deliberate—not an accidental oversight.

  3. Usage context: Only one call site uses skipCache=true (packages/clerk-js/src/core/clerk.ts:2774) during outage recovery. This scenario doesn't have multiple concurrent calls, so the edge case doesn't manifest in practice.

The implementation is sound: skipCache means "bypass my local cache read for this call" (ensuring fresh data), while still caching results for future calls (and for skipCache=false calls in other tabs). The double-check-lock pattern correctly prevents duplicate fetches between skipCache=false callers.

Comment on lines +449 to +451
// Cache the promise immediately to prevent concurrent calls from triggering duplicate requests
SessionTokenCache.set({ tokenId, tokenResolver });
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's interesting that we have this here yet it doesn't solve the race condition. I suppose if N getToken calls are fired at the same time it would happen.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
.changeset/fix-token-refresh-race-condition.md (1)

5-5: Refine the message to focus more on user impact than implementation.

While this is already much improved, the mention of "cross-tab lock" remains implementation-focused. Consider rephrasing to emphasize the user-visible benefit (proper synchronization) rather than the mechanism (locking).

Suggested alternative:

-Fix race condition where multiple browser tabs could fetch session tokens simultaneously. `getToken()` now uses a cross-tab lock to coordinate token refresh operations
+Fix race condition where multiple browser tabs could fetch session tokens simultaneously. Token refresh operations are now properly coordinated across tabs.

This version emphasizes what users care about (proper synchronization) without describing the technical mechanism. Aligns with the feedback that release notes should be "light on implementation details and focus on the user impact."

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between f94b214 and be3c3a9.

📒 Files selected for processing (1)
  • .changeset/fix-token-refresh-race-condition.md (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Formatting | Dedupe | Changeset
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: semgrep-cloud-platform/scan
  • GitHub Check: semgrep-cloud-platform/scan

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (4)
packages/clerk-js/src/utils/__tests__/lru-map.test.ts (1)

5-73: Test coverage is comprehensive for core LRU behaviors.

The tests cover the essential LRU functionality: basic operations, eviction, MRU promotion, updates, and edge case with maxSize=1.

Consider adding tests for these edge cases:

  1. maxSize of 0: The implementation's while (this.size >= this.maxSize) loop could behave unexpectedly or loop infinitely if maxSize is 0.
  2. Storing undefined as a value: The get() implementation uses if (value !== undefined) to decide whether to refresh access, meaning entries with undefined values won't be promoted to MRU position.
+ it('handles storing undefined as a value', () => {
+   const map = new LruMap<string, number | undefined>(3);
+   map.set('a', undefined);
+   map.set('b', 2);
+   map.set('c', 3);
+   
+   // Access 'a' - should still exist but won't be promoted due to implementation
+   expect(map.get('a')).toBeUndefined();
+   expect(map.has('a')).toBe(true);
+ });
+
+ it('throws or handles maxSize of 0', () => {
+   // Document expected behavior
+   const map = new LruMap<string, number>(0);
+   // This could cause issues - verify expected behavior
+ });
packages/clerk-js/src/utils/lru-map.ts (3)

10-17: Edge case: entries with undefined values won't receive MRU promotion.

The condition if (value !== undefined) prevents MRU promotion for entries that store undefined as a value. If a key maps to undefined, get() returns it but doesn't refresh its position, making it vulnerable to early eviction.

For the current usage (storing SafeLock objects), this isn't an issue. However, for a generic utility class, consider using super.has(key) instead:

 override get(key: K): V | undefined {
-  const value = super.get(key);
-  if (value !== undefined) {
+  if (super.has(key)) {
+    const value = super.get(key);
     super.delete(key);
     super.set(key, value);
+    return value;
   }
-  return value;
+  return undefined;
 }

5-8: Consider validating maxSize in the constructor.

A maxSize of 0 or negative would result in an LruMap that can never store entries (all sets immediately evict). While the implementation handles this gracefully due to the break condition, explicit validation would prevent misuse and clarify intent.

 constructor(private maxSize: number) {
   super();
+  if (maxSize < 1) {
+    throw new Error('LruMap maxSize must be at least 1');
+  }
 }

1-8: JSDoc could be enhanced with @param and @example tags.

Per coding guidelines, public APIs should include comprehensive JSDoc with @param, @returns, @throws, and @example tags.

 /**
  * A simple Map with LRU (Least Recently Used) eviction.
  * When the map exceeds maxSize, the oldest entries are removed.
+ *
+ * @template K - The type of map keys
+ * @template V - The type of map values
+ * @param maxSize - Maximum number of entries before eviction occurs (must be >= 1)
+ *
+ * @example
+ * const cache = new LruMap<string, number>(3);
+ * cache.set('a', 1);
+ * cache.get('a'); // Returns 1 and marks 'a' as most recently used
  */
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between be3c3a9 and 4a6769d.

📒 Files selected for processing (3)
  • packages/clerk-js/src/core/resources/Session.ts (3 hunks)
  • packages/clerk-js/src/utils/__tests__/lru-map.test.ts (1 hunks)
  • packages/clerk-js/src/utils/lru-map.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (9)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

All code must pass ESLint checks with the project's configuration

Files:

  • packages/clerk-js/src/utils/lru-map.ts
  • packages/clerk-js/src/utils/__tests__/lru-map.test.ts
  • packages/clerk-js/src/core/resources/Session.ts
**/*.{js,jsx,ts,tsx,json,md,yml,yaml}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Use Prettier for consistent code formatting

Files:

  • packages/clerk-js/src/utils/lru-map.ts
  • packages/clerk-js/src/utils/__tests__/lru-map.test.ts
  • packages/clerk-js/src/core/resources/Session.ts
packages/**/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

TypeScript is required for all packages

Files:

  • packages/clerk-js/src/utils/lru-map.ts
  • packages/clerk-js/src/utils/__tests__/lru-map.test.ts
  • packages/clerk-js/src/core/resources/Session.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Follow established naming conventions (PascalCase for components, camelCase for variables)

Prefer importing types from @clerk/shared/types instead of the deprecated @clerk/types alias

Files:

  • packages/clerk-js/src/utils/lru-map.ts
  • packages/clerk-js/src/utils/__tests__/lru-map.test.ts
  • packages/clerk-js/src/core/resources/Session.ts
packages/**/src/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

packages/**/src/**/*.{ts,tsx,js,jsx}: Maintain comprehensive JSDoc comments for public APIs
Use tree-shaking friendly exports
Validate all inputs and sanitize outputs
All public APIs must be documented with JSDoc
Use dynamic imports for optional features
Provide meaningful error messages to developers
Include error recovery suggestions where applicable
Log errors appropriately for debugging
Lazy load components and features when possible
Implement proper caching strategies
Use efficient data structures and algorithms
Implement proper logging with different levels

Files:

  • packages/clerk-js/src/utils/lru-map.ts
  • packages/clerk-js/src/utils/__tests__/lru-map.test.ts
  • packages/clerk-js/src/core/resources/Session.ts
**/*.ts?(x)

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Use proper TypeScript error types

Files:

  • packages/clerk-js/src/utils/lru-map.ts
  • packages/clerk-js/src/utils/__tests__/lru-map.test.ts
  • packages/clerk-js/src/core/resources/Session.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/typescript.mdc)

**/*.{ts,tsx}: Always define explicit return types for functions, especially public APIs
Use proper type annotations for variables and parameters where inference isn't clear
Avoid any type - prefer unknown when type is uncertain, then narrow with type guards
Implement type guards for unknown types using the pattern function isType(value: unknown): value is Type
Use interface for object shapes that might be extended
Use type for unions, primitives, and computed types
Prefer readonly properties for immutable data structures
Use private for internal implementation details in classes
Use protected for inheritance hierarchies
Use public explicitly for clarity in public APIs
Use mixins for shared behavior across unrelated classes in TypeScript
Use generic constraints with bounded type parameters like <T extends { id: string }>
Use utility types like Omit, Partial, and Pick for data transformation instead of manual type construction
Use discriminated unions instead of boolean flags for state management and API responses
Use mapped types for transforming object types
Use conditional types for type-level logic
Leverage template literal types for string manipulation at the type level
Use ES6 imports/exports consistently
Use default exports sparingly, prefer named exports
Document functions with JSDoc comments including @param, @returns, @throws, and @example tags
Create custom error classes that extend Error for specific error types
Use the Result pattern for error handling instead of throwing exceptions
Use optional chaining (?.) and nullish coalescing (??) operators for safe property access
Let TypeScript infer obvious types to reduce verbosity
Use const assertions with as const for literal types
Use satisfies operator for type checking without widening types
Declare readonly arrays and objects for immutable data structures
Use spread operator and array spread for immutable updates instead of mutations
Use lazy loading for large types...

Files:

  • packages/clerk-js/src/utils/lru-map.ts
  • packages/clerk-js/src/utils/__tests__/lru-map.test.ts
  • packages/clerk-js/src/core/resources/Session.ts
**/*.{test,spec}.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

**/*.{test,spec}.{ts,tsx,js,jsx}: Unit tests are required for all new functionality
Verify proper error handling and edge cases
Include tests for all new features

Files:

  • packages/clerk-js/src/utils/__tests__/lru-map.test.ts
**/*.{test,spec,e2e}.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Use real Clerk instances for integration tests

Files:

  • packages/clerk-js/src/utils/__tests__/lru-map.test.ts
🧬 Code graph analysis (2)
packages/clerk-js/src/utils/__tests__/lru-map.test.ts (1)
packages/clerk-js/src/utils/lru-map.ts (1)
  • LruMap (5-34)
packages/clerk-js/src/core/resources/Session.ts (9)
packages/clerk-js/src/utils/lru-map.ts (1)
  • LruMap (5-34)
packages/clerk-js/src/core/auth/safeLock.ts (1)
  • SafeLock (15-62)
packages/clerk-js/src/core/tokenCache.ts (1)
  • SessionTokenCache (413-413)
packages/clerk-js/src/utils/debug.ts (1)
  • debugLogger (150-179)
packages/clerk-js/src/core/events.ts (2)
  • eventBus (32-32)
  • events (7-15)
packages/clerk-js/rspack.config.js (1)
  • path (4-4)
packages/clerk-js/src/core/resources/Client.ts (1)
  • path (161-163)
packages/clerk-js/src/core/resources/User.ts (1)
  • path (111-113)
packages/clerk-js/src/core/resources/Token.ts (1)
  • Token (7-57)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (34)
  • GitHub Check: Integration Tests (quickstart, chrome, 15)
  • GitHub Check: Integration Tests (nextjs, chrome, 15, RQ)
  • GitHub Check: Integration Tests (quickstart, chrome, 16)
  • GitHub Check: Integration Tests (nextjs, chrome, 14)
  • GitHub Check: Integration Tests (react-router, chrome)
  • GitHub Check: Integration Tests (custom, chrome)
  • GitHub Check: Integration Tests (nextjs, chrome, 16)
  • GitHub Check: Integration Tests (nextjs, chrome, 15)
  • GitHub Check: Integration Tests (machine, chrome, RQ)
  • GitHub Check: Integration Tests (expo-web, chrome)
  • GitHub Check: Integration Tests (machine, chrome)
  • GitHub Check: Integration Tests (billing, chrome, RQ)
  • GitHub Check: Integration Tests (handshake:staging, chrome)
  • GitHub Check: Integration Tests (billing, chrome)
  • GitHub Check: Integration Tests (astro, chrome)
  • GitHub Check: Integration Tests (tanstack-react-start, chrome)
  • GitHub Check: Integration Tests (vue, chrome)
  • GitHub Check: Integration Tests (nuxt, chrome)
  • GitHub Check: Integration Tests (handshake, chrome)
  • GitHub Check: Integration Tests (localhost, chrome)
  • GitHub Check: Integration Tests (sessions, chrome)
  • GitHub Check: Integration Tests (sessions:staging, chrome)
  • GitHub Check: Integration Tests (generic, chrome)
  • GitHub Check: Integration Tests (express, chrome)
  • GitHub Check: Integration Tests (ap-flows, chrome)
  • GitHub Check: Integration Tests (elements, chrome)
  • GitHub Check: Publish with pkg-pr-new
  • GitHub Check: Unit Tests (22, **)
  • GitHub Check: Unit Tests (22, shared, clerk-js, RQ)
  • GitHub Check: Static analysis
  • GitHub Check: Formatting | Dedupe | Changeset
  • GitHub Check: semgrep-cloud-platform/scan
  • GitHub Check: semgrep-cloud-platform/scan
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (4)
packages/clerk-js/src/core/resources/Session.ts (4)

46-64: Well-designed per-tokenId locking with bounded memory.

The implementation correctly uses LRU eviction to prevent unbounded growth while maintaining per-tokenId locks for parallel fetches of different token types.

The design is safe even if a SafeLock wrapper is evicted while the underlying browser lock is held—navigator.locks.request is keyed by the string key (clerk.lock.getToken.${tokenId}), not the wrapper object. A newly created SafeLock for the same tokenId will still correctly coordinate with the existing browser lock.


394-407: LGTM: Fast path avoids lock overhead for cache hits.

The unlocked cache check for immediate hits is the correct optimization pattern—it avoids cross-tab lock acquisition overhead for the common case where a valid token is already cached.


409-466: Double-checked locking pattern correctly implemented.

The implementation follows the standard pattern:

  1. Check cache without lock (fast path)
  2. Acquire lock on miss
  3. Re-check cache after lock (another tab may have populated it)
  4. Fetch and cache if still missing

One observation: when skipCache is true, the code still acquires the lock but always performs a fetch. This is intentional and correct—it ensures that even explicit cache-bypassing requests are serialized to prevent thundering herd on the API.


447-450: Based on my verification of the codebase, I can now provide the rewritten review comment:


The concern about rejected promises being cached is not valid — rejected promises are automatically cleaned up.

The tokenResolver promise is cached immediately at line 450, but if Token.create() rejects, the tokenCache.ts implementation automatically removes the entry via its .catch() handler (line 368: deleteKey()). This means failed token fetches do not persist in cache. On retry (line 130-137 of Session.ts), the cache miss triggers a fresh Token.create() call, allowing retries to succeed.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (2)
packages/clerk-js/src/core/auth/safeLock.ts (2)

30-32: Document the SafeLockReturn public interface with JSDoc

SafeLockReturn is exported and used as the public return type of SafeLock, but it currently has no JSDoc. Project guidelines require JSDoc for public APIs, and a similar comment was raised earlier in this PR.

Adding documentation here will clarify how callers should use the interface and what guarantees acquireLockAndRun provides (single execution, lock behavior, degraded mode semantics).

For example:

+/**
+ * Public return type for {@link SafeLock}, representing a cross-tab lock handle.
+ *
+ * @public
+ */
 export interface SafeLockReturn {
-  acquireLockAndRun: <T>(cb: () => Promise<T>) => Promise<T>;
+  /**
+   * Acquires a cross-tab lock and runs the provided callback while holding it.
+   *
+   * In degraded mode (e.g. lock acquisition timeout), the callback is still
+   * executed without the lock to avoid dropping the operation.
+   *
+   * @typeParam T - The resolved value type of the callback.
+   * @param cb - Async callback to run under the lock.
+   * @returns A promise that resolves or rejects with the callback’s result.
+   */
+  acquireLockAndRun: <T>(cb: () => Promise<T>) => Promise<T>;
 }

Please ensure the JSDoc tags and wording align with your existing documentation conventions (e.g. whether you prefer @public or another visibility tag).


34-42: Extend SafeLock JSDoc with @returns and an explicit usage example

The SafeLock function’s JSDoc explains the purpose and @param but is missing the @returns and @example tags required by the project’s API documentation guidelines and prior review feedback.

To make the public API self-describing and consistent:

 /**
  * Creates a cross-tab lock for coordinating exclusive operations across browser tabs.
  *
  * Uses Web Locks API in secure contexts (HTTPS), falling back to browser-tabs-lock
  * (localStorage-based) in non-secure contexts.
  *
  * @param key - Unique identifier for the lock (same key = same lock across all tabs)
+ * @returns A {@link SafeLockReturn} instance exposing {@link SafeLockReturn.acquireLockAndRun}.
+ * @example
+ * ```ts
+ * const lock = SafeLock('session-token-refresh');
+ * await lock.acquireLockAndRun(async () => {
+ *   // Perform token refresh or other critical section here.
+ * });
+ * ```
  */
 export function SafeLock(key: string): SafeLockReturn {

This keeps the implementation unchanged while improving discoverability for consumers.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 1422bcd and 5552fb5.

📒 Files selected for processing (3)
  • packages/clerk-js/src/core/auth/__tests__/safeLock.test.ts (1 hunks)
  • packages/clerk-js/src/core/auth/safeLock.ts (1 hunks)
  • packages/clerk-js/src/core/resources/__tests__/Session.test.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (9)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

All code must pass ESLint checks with the project's configuration

Files:

  • packages/clerk-js/src/core/resources/__tests__/Session.test.ts
  • packages/clerk-js/src/core/auth/safeLock.ts
  • packages/clerk-js/src/core/auth/__tests__/safeLock.test.ts
**/*.{js,jsx,ts,tsx,json,md,yml,yaml}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Use Prettier for consistent code formatting

Files:

  • packages/clerk-js/src/core/resources/__tests__/Session.test.ts
  • packages/clerk-js/src/core/auth/safeLock.ts
  • packages/clerk-js/src/core/auth/__tests__/safeLock.test.ts
packages/**/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

TypeScript is required for all packages

Files:

  • packages/clerk-js/src/core/resources/__tests__/Session.test.ts
  • packages/clerk-js/src/core/auth/safeLock.ts
  • packages/clerk-js/src/core/auth/__tests__/safeLock.test.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Follow established naming conventions (PascalCase for components, camelCase for variables)

Prefer importing types from @clerk/shared/types instead of the deprecated @clerk/types alias

Files:

  • packages/clerk-js/src/core/resources/__tests__/Session.test.ts
  • packages/clerk-js/src/core/auth/safeLock.ts
  • packages/clerk-js/src/core/auth/__tests__/safeLock.test.ts
packages/**/src/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

packages/**/src/**/*.{ts,tsx,js,jsx}: Maintain comprehensive JSDoc comments for public APIs
Use tree-shaking friendly exports
Validate all inputs and sanitize outputs
All public APIs must be documented with JSDoc
Use dynamic imports for optional features
Provide meaningful error messages to developers
Include error recovery suggestions where applicable
Log errors appropriately for debugging
Lazy load components and features when possible
Implement proper caching strategies
Use efficient data structures and algorithms
Implement proper logging with different levels

Files:

  • packages/clerk-js/src/core/resources/__tests__/Session.test.ts
  • packages/clerk-js/src/core/auth/safeLock.ts
  • packages/clerk-js/src/core/auth/__tests__/safeLock.test.ts
**/*.{test,spec}.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

**/*.{test,spec}.{ts,tsx,js,jsx}: Unit tests are required for all new functionality
Verify proper error handling and edge cases
Include tests for all new features

Files:

  • packages/clerk-js/src/core/resources/__tests__/Session.test.ts
  • packages/clerk-js/src/core/auth/__tests__/safeLock.test.ts
**/*.ts?(x)

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Use proper TypeScript error types

Files:

  • packages/clerk-js/src/core/resources/__tests__/Session.test.ts
  • packages/clerk-js/src/core/auth/safeLock.ts
  • packages/clerk-js/src/core/auth/__tests__/safeLock.test.ts
**/*.{test,spec,e2e}.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Use real Clerk instances for integration tests

Files:

  • packages/clerk-js/src/core/resources/__tests__/Session.test.ts
  • packages/clerk-js/src/core/auth/__tests__/safeLock.test.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/typescript.mdc)

**/*.{ts,tsx}: Always define explicit return types for functions, especially public APIs
Use proper type annotations for variables and parameters where inference isn't clear
Avoid any type - prefer unknown when type is uncertain, then narrow with type guards
Implement type guards for unknown types using the pattern function isType(value: unknown): value is Type
Use interface for object shapes that might be extended
Use type for unions, primitives, and computed types
Prefer readonly properties for immutable data structures
Use private for internal implementation details in classes
Use protected for inheritance hierarchies
Use public explicitly for clarity in public APIs
Use mixins for shared behavior across unrelated classes in TypeScript
Use generic constraints with bounded type parameters like <T extends { id: string }>
Use utility types like Omit, Partial, and Pick for data transformation instead of manual type construction
Use discriminated unions instead of boolean flags for state management and API responses
Use mapped types for transforming object types
Use conditional types for type-level logic
Leverage template literal types for string manipulation at the type level
Use ES6 imports/exports consistently
Use default exports sparingly, prefer named exports
Document functions with JSDoc comments including @param, @returns, @throws, and @example tags
Create custom error classes that extend Error for specific error types
Use the Result pattern for error handling instead of throwing exceptions
Use optional chaining (?.) and nullish coalescing (??) operators for safe property access
Let TypeScript infer obvious types to reduce verbosity
Use const assertions with as const for literal types
Use satisfies operator for type checking without widening types
Declare readonly arrays and objects for immutable data structures
Use spread operator and array spread for immutable updates instead of mutations
Use lazy loading for large types...

Files:

  • packages/clerk-js/src/core/resources/__tests__/Session.test.ts
  • packages/clerk-js/src/core/auth/safeLock.ts
  • packages/clerk-js/src/core/auth/__tests__/safeLock.test.ts
🧬 Code graph analysis (2)
packages/clerk-js/src/core/resources/__tests__/Session.test.ts (2)
packages/clerk-js/src/core/resources/Session.ts (1)
  • Session (66-467)
packages/clerk-js/src/core/tokenCache.ts (1)
  • SessionTokenCache (413-413)
packages/clerk-js/src/core/auth/__tests__/safeLock.test.ts (1)
packages/clerk-js/src/core/auth/safeLock.ts (2)
  • SafeLock (42-87)
  • SafeLockReturn (30-32)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Build Packages
  • GitHub Check: Formatting | Dedupe | Changeset
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: semgrep-cloud-platform/scan
  • GitHub Check: semgrep-cloud-platform/scan
🔇 Additional comments (2)
packages/clerk-js/src/core/resources/__tests__/Session.test.ts (1)

430-492: New cache + skipCache tests accurately exercise token cache semantics

Both tests correctly assert that:

  • Once the token cache is populated, subsequent getToken() calls reuse it without extra FAPI requests.
  • skipCache: true bypasses the cache and forces an API call even when SessionTokenCache is hydrated.

This is good, non-flaky coverage for the new locking/caching behavior.

packages/clerk-js/src/core/auth/__tests__/safeLock.test.ts (1)

60-118: SafeLock test suite nicely exercises interface, sharing, and lifecycle behavior

The “shared lock pattern” and “beforeunload listener consolidation” tests are well structured:

  • Demonstrate mocking SafeLockReturn for sequential execution and multi-consumer sharing.
  • Verify only a single beforeunload listener is registered per module instance, and that a fresh import starts clean.

This gives strong confidence in SafeLock’s intended usage patterns without over-coupling to implementation details.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (2)
packages/clerk-js/src/core/auth/safeLock.ts (2)

23-35: Add/restore full JSDoc for SafeLockReturn and complete SafeLock docs

SafeLockReturn (Line 23) is exported, and SafeLock (Line 35) is a public helper, but:

  • SafeLockReturn currently has no JSDoc at all.
  • SafeLock’s JSDoc has @param but is missing the required @returns and @example tags per the coding guidelines.

Given these are part of the public locking API, please add/restore comprehensive JSDoc, e.g.:

+/**
+ * @public
+ * Return type for {@link SafeLock}, exposing a helper that serializes async work under a cross-tab lock.
+ *
+ * @example
+ * const { acquireLockAndRun } = SafeLock('token-refresh');
+ * const result = await acquireLockAndRun(async () => {
+ *   // refresh session token here
+ *   return 'ok';
+ * });
+ */
 export interface SafeLockReturn {
+  /**
+   * Acquires the underlying lock and runs the provided callback.
+   *
+   * @param cb - Async callback to execute while holding the lock.
+   * @returns A promise resolving to the callback result.
+   */
   acquireLockAndRun: <T>(cb: () => Promise<T>) => Promise<T>;
 }
 
 /**
  * Creates a cross-tab lock for coordinating exclusive operations across browser tabs.
  *
  * Uses Web Locks API in secure contexts (HTTPS), falling back to browser-tabs-lock
  * (localStorage-based) in non-secure contexts.
  *
  * @param key - Unique identifier for the lock (same key = same lock across all tabs)
+ * @returns An object exposing {@link SafeLockReturn.acquireLockAndRun} to execute work under the lock.
+ * @example
+ * const safeLock = SafeLock('my-lock-key');
+ * await safeLock.acquireLockAndRun(async () => {
+ *   // critical section
+ * });
  */
 export function SafeLock(key: string): SafeLockReturn {

This aligns with the project’s JSDoc requirements for public APIs.


41-54: Avoid double‑invoking the callback when Web Locks request rejects

In the Web Locks path, the catch block at Line 51 treats any rejection from navigator.locks.request as a lock timeout and calls cb() again. If cb() itself throws or returns a rejected promise while holding the lock, navigator.locks.request will reject with that error, and this code will:

  • Execute cb() a second time (breaking non‑idempotent flows like token refresh).
  • Mis‑log the failure as a lock timeout/degraded mode.
  • Potentially mask the original error with the second invocation’s outcome.

You should distinguish lock‑acquisition timeouts (AbortError) from callback errors and only fall back to degraded mode for the former; all other errors should be re‑thrown. Also, the timeout should be cleared in both success and error paths.

A possible fix:

-      try {
-        return await navigator.locks.request(key, { signal: controller.signal }, async () => {
-          clearTimeout(lockTimeout);
-          return await cb();
-        });
-      } catch {
-        debugLogger.warn('Lock acquisition timed out, proceeding without lock (degraded mode)', { key }, 'safeLock');
-        return await cb();
-      }
+      try {
+        return await navigator.locks.request(key, { signal: controller.signal }, async () => {
+          clearTimeout(lockTimeout);
+          return await cb();
+        });
+      } catch (error) {
+        clearTimeout(lockTimeout);
+
+        // Only fall back to degraded mode when the lock acquisition itself was aborted/timed out.
+        if (error instanceof DOMException && error.name === 'AbortError') {
+          debugLogger.warn(
+            'Lock acquisition timed out, proceeding without lock (degraded mode)',
+            { key },
+            'safeLock',
+          );
+          return await cb();
+        }
+
+        // Propagate callback errors and unexpected failures.
+        throw error;
+      }

This preserves the degraded‑mode behavior for genuine lock timeouts while ensuring callback errors are propagated and not retried. You may want to add/adjust tests in safeLock.test.ts to cover: (1) a callback that rejects under Web Locks is invoked exactly once and the error bubbles, and (2) an AbortError timeout falls back to degraded mode and runs cb() once.

🧹 Nitpick comments (1)
packages/clerk-js/src/core/resources/__tests__/Session.test.ts (1)

430-482: New getToken cache/skipCache tests look good; consider a stronger cross‑tab simulation

The two added tests correctly verify:

  • Cache reuse so subsequent getToken() calls avoid extra API requests.
  • That skipCache: true bypasses the cache and still returns the expected token.

One nit: the "uses cache populated by another source during lock wait (simulates cross-tab)" test currently uses a single Session instance with sequential calls, so it doesn’t actually exercise the per‑token lock behavior under concurrent callers (the main cross‑tab race the PR addresses).

If you want to more directly cover the cross‑tab scenario, consider (optionally) updating that test to:

  • Use two Session instances (or one per “tab”) sharing SessionTokenCache.
  • Call Promise.all([session1.getToken(), session2.getToken()]) and assert that only one API request occurs and both callers receive the same token.

As‑is, the tests are valid and safe to keep.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 5552fb5 and 9f338f4.

📒 Files selected for processing (4)
  • packages/clerk-js/src/core/auth/SessionCookiePoller.ts (2 hunks)
  • packages/clerk-js/src/core/auth/safeLock.ts (1 hunks)
  • packages/clerk-js/src/core/resources/Session.ts (2 hunks)
  • packages/clerk-js/src/core/resources/__tests__/Session.test.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/clerk-js/src/core/auth/SessionCookiePoller.ts
  • packages/clerk-js/src/core/resources/Session.ts
🧰 Additional context used
📓 Path-based instructions (9)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

All code must pass ESLint checks with the project's configuration

Files:

  • packages/clerk-js/src/core/resources/__tests__/Session.test.ts
  • packages/clerk-js/src/core/auth/safeLock.ts
**/*.{js,jsx,ts,tsx,json,md,yml,yaml}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Use Prettier for consistent code formatting

Files:

  • packages/clerk-js/src/core/resources/__tests__/Session.test.ts
  • packages/clerk-js/src/core/auth/safeLock.ts
packages/**/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

TypeScript is required for all packages

Files:

  • packages/clerk-js/src/core/resources/__tests__/Session.test.ts
  • packages/clerk-js/src/core/auth/safeLock.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Follow established naming conventions (PascalCase for components, camelCase for variables)

Prefer importing types from @clerk/shared/types instead of the deprecated @clerk/types alias

Files:

  • packages/clerk-js/src/core/resources/__tests__/Session.test.ts
  • packages/clerk-js/src/core/auth/safeLock.ts
packages/**/src/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

packages/**/src/**/*.{ts,tsx,js,jsx}: Maintain comprehensive JSDoc comments for public APIs
Use tree-shaking friendly exports
Validate all inputs and sanitize outputs
All public APIs must be documented with JSDoc
Use dynamic imports for optional features
Provide meaningful error messages to developers
Include error recovery suggestions where applicable
Log errors appropriately for debugging
Lazy load components and features when possible
Implement proper caching strategies
Use efficient data structures and algorithms
Implement proper logging with different levels

Files:

  • packages/clerk-js/src/core/resources/__tests__/Session.test.ts
  • packages/clerk-js/src/core/auth/safeLock.ts
**/*.{test,spec}.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

**/*.{test,spec}.{ts,tsx,js,jsx}: Unit tests are required for all new functionality
Verify proper error handling and edge cases
Include tests for all new features

Files:

  • packages/clerk-js/src/core/resources/__tests__/Session.test.ts
**/*.ts?(x)

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Use proper TypeScript error types

Files:

  • packages/clerk-js/src/core/resources/__tests__/Session.test.ts
  • packages/clerk-js/src/core/auth/safeLock.ts
**/*.{test,spec,e2e}.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Use real Clerk instances for integration tests

Files:

  • packages/clerk-js/src/core/resources/__tests__/Session.test.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/typescript.mdc)

**/*.{ts,tsx}: Always define explicit return types for functions, especially public APIs
Use proper type annotations for variables and parameters where inference isn't clear
Avoid any type - prefer unknown when type is uncertain, then narrow with type guards
Implement type guards for unknown types using the pattern function isType(value: unknown): value is Type
Use interface for object shapes that might be extended
Use type for unions, primitives, and computed types
Prefer readonly properties for immutable data structures
Use private for internal implementation details in classes
Use protected for inheritance hierarchies
Use public explicitly for clarity in public APIs
Use mixins for shared behavior across unrelated classes in TypeScript
Use generic constraints with bounded type parameters like <T extends { id: string }>
Use utility types like Omit, Partial, and Pick for data transformation instead of manual type construction
Use discriminated unions instead of boolean flags for state management and API responses
Use mapped types for transforming object types
Use conditional types for type-level logic
Leverage template literal types for string manipulation at the type level
Use ES6 imports/exports consistently
Use default exports sparingly, prefer named exports
Document functions with JSDoc comments including @param, @returns, @throws, and @example tags
Create custom error classes that extend Error for specific error types
Use the Result pattern for error handling instead of throwing exceptions
Use optional chaining (?.) and nullish coalescing (??) operators for safe property access
Let TypeScript infer obvious types to reduce verbosity
Use const assertions with as const for literal types
Use satisfies operator for type checking without widening types
Declare readonly arrays and objects for immutable data structures
Use spread operator and array spread for immutable updates instead of mutations
Use lazy loading for large types...

Files:

  • packages/clerk-js/src/core/resources/__tests__/Session.test.ts
  • packages/clerk-js/src/core/auth/safeLock.ts
🧬 Code graph analysis (2)
packages/clerk-js/src/core/resources/__tests__/Session.test.ts (3)
packages/clerk-js/src/test/core-fixtures.ts (1)
  • clerkMock (249-256)
packages/clerk-js/src/core/resources/Session.ts (1)
  • Session (57-448)
packages/clerk-js/src/core/tokenCache.ts (1)
  • SessionTokenCache (413-413)
packages/clerk-js/src/core/auth/safeLock.ts (1)
packages/clerk-js/src/utils/debug.ts (1)
  • debugLogger (150-179)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (32)
  • GitHub Check: Integration Tests (billing, chrome)
  • GitHub Check: Integration Tests (machine, chrome, RQ)
  • GitHub Check: Integration Tests (machine, chrome)
  • GitHub Check: Integration Tests (custom, chrome)
  • GitHub Check: Integration Tests (billing, chrome, RQ)
  • GitHub Check: Integration Tests (quickstart, chrome, 15)
  • GitHub Check: Integration Tests (nextjs, chrome, 15, RQ)
  • GitHub Check: Integration Tests (quickstart, chrome, 16)
  • GitHub Check: Integration Tests (nextjs, chrome, 14)
  • GitHub Check: Integration Tests (astro, chrome)
  • GitHub Check: Integration Tests (nextjs, chrome, 16)
  • GitHub Check: Integration Tests (nextjs, chrome, 15)
  • GitHub Check: Integration Tests (handshake, chrome)
  • GitHub Check: Integration Tests (react-router, chrome)
  • GitHub Check: Integration Tests (nuxt, chrome)
  • GitHub Check: Integration Tests (vue, chrome)
  • GitHub Check: Integration Tests (elements, chrome)
  • GitHub Check: Integration Tests (expo-web, chrome)
  • GitHub Check: Integration Tests (sessions, chrome)
  • GitHub Check: Integration Tests (tanstack-react-start, chrome)
  • GitHub Check: Integration Tests (localhost, chrome)
  • GitHub Check: Integration Tests (handshake:staging, chrome)
  • GitHub Check: Integration Tests (sessions:staging, chrome)
  • GitHub Check: Integration Tests (ap-flows, chrome)
  • GitHub Check: Integration Tests (express, chrome)
  • GitHub Check: Integration Tests (generic, chrome)
  • GitHub Check: Publish with pkg-pr-new
  • GitHub Check: Unit Tests (22, **)
  • GitHub Check: Unit Tests (22, shared, clerk-js, RQ)
  • GitHub Check: Static analysis
  • GitHub Check: semgrep-cloud-platform/scan
  • GitHub Check: semgrep-cloud-platform/scan

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 9f338f4 and 6d15034.

📒 Files selected for processing (2)
  • packages/clerk-js/src/core/auth/__tests__/safeLock.test.ts (1 hunks)
  • packages/clerk-js/src/core/auth/safeLock.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (9)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

All code must pass ESLint checks with the project's configuration

Files:

  • packages/clerk-js/src/core/auth/safeLock.ts
  • packages/clerk-js/src/core/auth/__tests__/safeLock.test.ts
**/*.{js,jsx,ts,tsx,json,md,yml,yaml}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Use Prettier for consistent code formatting

Files:

  • packages/clerk-js/src/core/auth/safeLock.ts
  • packages/clerk-js/src/core/auth/__tests__/safeLock.test.ts
packages/**/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

TypeScript is required for all packages

Files:

  • packages/clerk-js/src/core/auth/safeLock.ts
  • packages/clerk-js/src/core/auth/__tests__/safeLock.test.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Follow established naming conventions (PascalCase for components, camelCase for variables)

Prefer importing types from @clerk/shared/types instead of the deprecated @clerk/types alias

Files:

  • packages/clerk-js/src/core/auth/safeLock.ts
  • packages/clerk-js/src/core/auth/__tests__/safeLock.test.ts
packages/**/src/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

packages/**/src/**/*.{ts,tsx,js,jsx}: Maintain comprehensive JSDoc comments for public APIs
Use tree-shaking friendly exports
Validate all inputs and sanitize outputs
All public APIs must be documented with JSDoc
Use dynamic imports for optional features
Provide meaningful error messages to developers
Include error recovery suggestions where applicable
Log errors appropriately for debugging
Lazy load components and features when possible
Implement proper caching strategies
Use efficient data structures and algorithms
Implement proper logging with different levels

Files:

  • packages/clerk-js/src/core/auth/safeLock.ts
  • packages/clerk-js/src/core/auth/__tests__/safeLock.test.ts
**/*.ts?(x)

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Use proper TypeScript error types

Files:

  • packages/clerk-js/src/core/auth/safeLock.ts
  • packages/clerk-js/src/core/auth/__tests__/safeLock.test.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/typescript.mdc)

**/*.{ts,tsx}: Always define explicit return types for functions, especially public APIs
Use proper type annotations for variables and parameters where inference isn't clear
Avoid any type - prefer unknown when type is uncertain, then narrow with type guards
Implement type guards for unknown types using the pattern function isType(value: unknown): value is Type
Use interface for object shapes that might be extended
Use type for unions, primitives, and computed types
Prefer readonly properties for immutable data structures
Use private for internal implementation details in classes
Use protected for inheritance hierarchies
Use public explicitly for clarity in public APIs
Use mixins for shared behavior across unrelated classes in TypeScript
Use generic constraints with bounded type parameters like <T extends { id: string }>
Use utility types like Omit, Partial, and Pick for data transformation instead of manual type construction
Use discriminated unions instead of boolean flags for state management and API responses
Use mapped types for transforming object types
Use conditional types for type-level logic
Leverage template literal types for string manipulation at the type level
Use ES6 imports/exports consistently
Use default exports sparingly, prefer named exports
Document functions with JSDoc comments including @param, @returns, @throws, and @example tags
Create custom error classes that extend Error for specific error types
Use the Result pattern for error handling instead of throwing exceptions
Use optional chaining (?.) and nullish coalescing (??) operators for safe property access
Let TypeScript infer obvious types to reduce verbosity
Use const assertions with as const for literal types
Use satisfies operator for type checking without widening types
Declare readonly arrays and objects for immutable data structures
Use spread operator and array spread for immutable updates instead of mutations
Use lazy loading for large types...

Files:

  • packages/clerk-js/src/core/auth/safeLock.ts
  • packages/clerk-js/src/core/auth/__tests__/safeLock.test.ts
**/*.{test,spec}.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

**/*.{test,spec}.{ts,tsx,js,jsx}: Unit tests are required for all new functionality
Verify proper error handling and edge cases
Include tests for all new features

Files:

  • packages/clerk-js/src/core/auth/__tests__/safeLock.test.ts
**/*.{test,spec,e2e}.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Use real Clerk instances for integration tests

Files:

  • packages/clerk-js/src/core/auth/__tests__/safeLock.test.ts
🧬 Code graph analysis (2)
packages/clerk-js/src/core/auth/safeLock.ts (2)
packages/shared/src/telemetry/collector.ts (1)
  • window (279-317)
packages/clerk-js/src/utils/debug.ts (1)
  • debugLogger (150-179)
packages/clerk-js/src/core/auth/__tests__/safeLock.test.ts (1)
packages/clerk-js/src/core/auth/safeLock.ts (2)
  • SafeLock (35-74)
  • SafeLockReturn (23-25)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Formatting | Dedupe | Changeset
  • GitHub Check: Build Packages
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: semgrep-cloud-platform/scan
  • GitHub Check: semgrep-cloud-platform/scan
🔇 Additional comments (6)
packages/clerk-js/src/core/auth/safeLock.ts (3)

51-58: Error handling correctly distinguishes AbortError from callback errors.

The fix properly addresses the previous concern about double-invocation. Now callback errors are re-thrown while only AbortError (timeout) triggers degraded mode. This ensures non-idempotent operations aren't executed twice.


23-25: JSDoc documentation addressed.

The interface and function now have appropriate JSDoc documentation as requested in previous reviews.


36-39: The original concern about orphaning locks is incorrect and not applicable.

SafeLock results are always cached in Session.ts (the only production caller), so the same key will never call SafeLock multiple times. Additionally, even if the same key were reused, it would not cause orphaned locks because:

  1. The Lock class from browser-tabs-lock uses the string key parameter to manage lock state, not the Lock instance itself
  2. Line 61 demonstrates that a single Lock instance can acquire/release multiple different keys via lock.acquireLock(key) and lock.releaseLock(key)
  3. The beforeunload cleanup iterates all entries in the activeLocks map and calls lock.releaseLock(key) for each, releasing the associated key regardless of which Lock instance stored it

No change needed to the implementation.

packages/clerk-js/src/core/auth/__tests__/safeLock.test.ts (3)

38-57: Web Locks test skip condition now correctly includes isSecureContext.

The skip condition at line 41 now checks !isSecureContext, aligning with the implementation logic. This addresses the previous review comment.


176-205: Thorough testing of listener consolidation and module state.

The tests properly verify that only one beforeunload listener is registered regardless of how many locks are created, and that fresh module imports start with clean state.


60-117: Good coverage of shared lock patterns with mocks.

The mock-based tests effectively demonstrate the intended usage pattern where multiple components share a lock via the SafeLockReturn interface, validating sequential execution and correct invocation counts.

@jacekradko
Copy link
Member Author

@brkalow mind taking another look?

Comment on lines 385 to 381
if (shouldDispatchTokenUpdate) {
eventBus.emit(events.TokenUpdate, { token: cachedToken });
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ what's the purpose of this extra token update emit?

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/clerk-js/src/core/auth/safeLock.ts (2)

65-73: Inconsistent error handling between lock paths.

The browser-tabs-lock fallback (lines 65-71) propagates callback errors naturally via the try/finally pattern, while the Web Locks path (lines 53-62) swallows all errors. This creates inconsistent behavior depending on which lock mechanism is used.

Consider aligning the error handling so both paths propagate callback errors consistently.


48-62: Callback errors are silently swallowed and mixed with lock acquisition failures.

The catch block at lines 58–61 indiscriminately catches all rejections—both lock acquisition failures (DOMException with AbortError, SecurityError, etc.) and errors thrown by the callback itself. When cb() throws, the error is swallowed and false is returned instead of propagating the error to the caller.

This masks bugs in callbacks, makes debugging difficult, and prevents callers from distinguishing between "lock not acquired" and "lock acquired but callback failed." The JSDoc at line 12 also documents only timeout behavior, not the full contract.

Additionally, clearTimeout(lockTimeout) is only called on success (line 55), not in the error path, potentially leaking timeouts if lock acquisition fails quickly.

Distinguish lock failures from callback errors:

       return await navigator.locks
         .request(key, { signal: controller.signal }, async () => {
           clearTimeout(lockTimeout);
           return await cb();
         })
-        .catch(() => {
+        .catch((error) => {
+          clearTimeout(lockTimeout);
+
+          // Only return false for lock acquisition failures (timeout/abort)
+          if (error instanceof DOMException && error.name === 'AbortError') {
-          // Lock request was aborted (timeout) or failed
-          // Return false to indicate lock was not acquired (matches browser-tabs-lock behavior)
-          return false;
+            return false;
+          }
+
+          // Propagate callback errors
+          throw error;
         });
♻️ Duplicate comments (1)
packages/clerk-js/src/core/auth/__tests__/safeLock.test.ts (1)

26-30: Align skip condition with implementation's isSecureContext check.

The implementation at safeLock.ts:49 checks 'locks' in navigator && isSecureContext before using Web Locks. This test only checks for navigator.locks:

if (!('locks' in navigator) || !navigator.locks) {
  return;
}

If navigator.locks exists but isSecureContext is false, the implementation uses the fallback path, but this test will still run and may exhibit unexpected behavior.

Update the skip condition to include isSecureContext:

-if (!('locks' in navigator) || !navigator.locks) {
+if (!('locks' in navigator) || !navigator.locks || !isSecureContext) {
   return;
 }
🧹 Nitpick comments (1)
packages/clerk-js/src/core/auth/__tests__/safeLock.test.ts (1)

83-104: Consider clarifying test intent.

This test uses a mock that executes callbacks immediately without actual lock serialization. While it validates the interface and call counts, it doesn't demonstrate true cross-tab lock behavior where the second call would wait for the first.

The test title "mock lock can simulate sequential execution" is accurate - it shows how to create mocks for component tests. Consider adding a brief comment to clarify this tests the mocking pattern, not actual lock semantics.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 6d15034 and 3303971.

📒 Files selected for processing (6)
  • .changeset/fix-token-refresh-race-condition.md (1 hunks)
  • packages/clerk-js/src/core/auth/AuthCookieService.ts (6 hunks)
  • packages/clerk-js/src/core/auth/SessionCookiePoller.ts (1 hunks)
  • packages/clerk-js/src/core/auth/__tests__/SessionCookiePoller.test.ts (1 hunks)
  • packages/clerk-js/src/core/auth/__tests__/safeLock.test.ts (1 hunks)
  • packages/clerk-js/src/core/auth/safeLock.ts (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/clerk-js/src/core/auth/tests/SessionCookiePoller.test.ts
  • packages/clerk-js/src/core/auth/SessionCookiePoller.ts
🧰 Additional context used
📓 Path-based instructions (9)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

All code must pass ESLint checks with the project's configuration

Files:

  • packages/clerk-js/src/core/auth/AuthCookieService.ts
  • packages/clerk-js/src/core/auth/safeLock.ts
  • packages/clerk-js/src/core/auth/__tests__/safeLock.test.ts
**/*.{js,jsx,ts,tsx,json,md,yml,yaml}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Use Prettier for consistent code formatting

Files:

  • packages/clerk-js/src/core/auth/AuthCookieService.ts
  • packages/clerk-js/src/core/auth/safeLock.ts
  • packages/clerk-js/src/core/auth/__tests__/safeLock.test.ts
packages/**/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

TypeScript is required for all packages

Files:

  • packages/clerk-js/src/core/auth/AuthCookieService.ts
  • packages/clerk-js/src/core/auth/safeLock.ts
  • packages/clerk-js/src/core/auth/__tests__/safeLock.test.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Follow established naming conventions (PascalCase for components, camelCase for variables)

Prefer importing types from @clerk/shared/types instead of the deprecated @clerk/types alias

Files:

  • packages/clerk-js/src/core/auth/AuthCookieService.ts
  • packages/clerk-js/src/core/auth/safeLock.ts
  • packages/clerk-js/src/core/auth/__tests__/safeLock.test.ts
packages/**/src/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

packages/**/src/**/*.{ts,tsx,js,jsx}: Maintain comprehensive JSDoc comments for public APIs
Use tree-shaking friendly exports
Validate all inputs and sanitize outputs
All public APIs must be documented with JSDoc
Use dynamic imports for optional features
Provide meaningful error messages to developers
Include error recovery suggestions where applicable
Log errors appropriately for debugging
Lazy load components and features when possible
Implement proper caching strategies
Use efficient data structures and algorithms
Implement proper logging with different levels

Files:

  • packages/clerk-js/src/core/auth/AuthCookieService.ts
  • packages/clerk-js/src/core/auth/safeLock.ts
  • packages/clerk-js/src/core/auth/__tests__/safeLock.test.ts
**/*.ts?(x)

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Use proper TypeScript error types

Files:

  • packages/clerk-js/src/core/auth/AuthCookieService.ts
  • packages/clerk-js/src/core/auth/safeLock.ts
  • packages/clerk-js/src/core/auth/__tests__/safeLock.test.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/typescript.mdc)

**/*.{ts,tsx}: Always define explicit return types for functions, especially public APIs
Use proper type annotations for variables and parameters where inference isn't clear
Avoid any type - prefer unknown when type is uncertain, then narrow with type guards
Implement type guards for unknown types using the pattern function isType(value: unknown): value is Type
Use interface for object shapes that might be extended
Use type for unions, primitives, and computed types
Prefer readonly properties for immutable data structures
Use private for internal implementation details in classes
Use protected for inheritance hierarchies
Use public explicitly for clarity in public APIs
Use mixins for shared behavior across unrelated classes in TypeScript
Use generic constraints with bounded type parameters like <T extends { id: string }>
Use utility types like Omit, Partial, and Pick for data transformation instead of manual type construction
Use discriminated unions instead of boolean flags for state management and API responses
Use mapped types for transforming object types
Use conditional types for type-level logic
Leverage template literal types for string manipulation at the type level
Use ES6 imports/exports consistently
Use default exports sparingly, prefer named exports
Document functions with JSDoc comments including @param, @returns, @throws, and @example tags
Create custom error classes that extend Error for specific error types
Use the Result pattern for error handling instead of throwing exceptions
Use optional chaining (?.) and nullish coalescing (??) operators for safe property access
Let TypeScript infer obvious types to reduce verbosity
Use const assertions with as const for literal types
Use satisfies operator for type checking without widening types
Declare readonly arrays and objects for immutable data structures
Use spread operator and array spread for immutable updates instead of mutations
Use lazy loading for large types...

Files:

  • packages/clerk-js/src/core/auth/AuthCookieService.ts
  • packages/clerk-js/src/core/auth/safeLock.ts
  • packages/clerk-js/src/core/auth/__tests__/safeLock.test.ts
**/*.{test,spec}.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

**/*.{test,spec}.{ts,tsx,js,jsx}: Unit tests are required for all new functionality
Verify proper error handling and edge cases
Include tests for all new features

Files:

  • packages/clerk-js/src/core/auth/__tests__/safeLock.test.ts
**/*.{test,spec,e2e}.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Use real Clerk instances for integration tests

Files:

  • packages/clerk-js/src/core/auth/__tests__/safeLock.test.ts
🧬 Code graph analysis (2)
packages/clerk-js/src/core/auth/AuthCookieService.ts (4)
packages/clerk-js/src/core/auth/cookies/clientUat.ts (1)
  • ClientUatCookieHandler (12-15)
packages/clerk-js/src/core/auth/SessionCookiePoller.ts (2)
  • SessionCookiePoller (27-60)
  • REFRESH_SESSION_TOKEN_LOCK_KEY (6-6)
packages/clerk-js/src/core/auth/cookies/session.ts (2)
  • SessionCookieHandler (10-14)
  • createSessionCookie (28-59)
packages/clerk-js/src/core/auth/safeLock.ts (2)
  • SafeLockReturn (6-15)
  • SafeLock (39-77)
packages/clerk-js/src/core/auth/__tests__/safeLock.test.ts (1)
packages/clerk-js/src/core/auth/safeLock.ts (2)
  • SafeLock (39-77)
  • SafeLockReturn (6-15)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: semgrep-cloud-platform/scan
  • GitHub Check: semgrep-cloud-platform/scan
🔇 Additional comments (10)
.changeset/fix-token-refresh-race-condition.md (1)

1-5: LGTM!

The changeset is well-written with a clear, user-focused description of the fix. It appropriately explains the impact without diving into implementation details.

packages/clerk-js/src/core/auth/AuthCookieService.ts (5)

23-25: LGTM!

The imports are well-organized, bringing in the necessary types and functions for cross-tab lock coordination.


75-78: LGTM!

The shared lock is correctly initialized early in the constructor using the exported constant key. The comments clearly explain the coordination purpose.


138-143: LGTM!

The poller correctly receives the shared lock, ensuring coordination with the focus handler.


152-168: LGTM!

The focus handler correctly uses the shared lock to coordinate token refresh. The added comments clearly explain both the microtask timing rationale and the cross-tab coordination purpose.


92-98: LGTM!

The initialization order changes are minor and don't affect functionality.

packages/clerk-js/src/core/auth/__tests__/safeLock.test.ts (1)

6-23: LGTM!

The interface contract tests effectively validate the public API shape and demonstrate mockability for consumer tests.

packages/clerk-js/src/core/auth/safeLock.ts (3)

3-15: LGTM!

The interface is well-documented with clear JSDoc explaining the cross-tab coordination behavior and return semantics.


17-39: LGTM!

Excellent documentation with a practical example demonstrating the cross-tab coordination pattern.


42-46: LGTM!

The beforeunload cleanup correctly prevents deadlocks when a tab holding a lock is closed.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
packages/clerk-js/src/core/auth/safeLock.ts (1)

39-44: Callback errors will trigger double execution in degraded mode.

The catch block catches all exceptions from navigator.locks.request, including errors thrown by the callback cb(). When cb() rejects while holding the lock, the Web Locks API propagates that rejection, which this catch block intercepts and then re-executes cb() in "degraded mode" - resulting in the callback running twice.

This is particularly problematic for token refresh operations which are not idempotent.

Apply this diff to distinguish lock acquisition failures from callback errors:

       try {
         return await navigator.locks.request(key, { signal: controller.signal }, async () => {
           clearTimeout(lockTimeout);
           return await cb();
         });
-      } catch {
-        // Lock request was aborted (timeout) or failed
-        // Execute callback anyway in degraded mode to ensure operation completes
-        debugLogger.warn('Lock acquisition timed out, proceeding without lock (degraded mode)', { key }, 'safeLock');
-        return await cb();
+      } catch (error) {
+        clearTimeout(lockTimeout);
+        // Only fall back to degraded mode for lock acquisition failures (AbortError from timeout)
+        if (error instanceof DOMException && error.name === 'AbortError') {
+          debugLogger.warn('Lock acquisition timed out, proceeding without lock (degraded mode)', { key }, 'safeLock');
+          return await cb();
+        }
+        // Re-throw callback errors to prevent double execution
+        throw error;
       }
🧹 Nitpick comments (3)
packages/clerk-js/src/core/auth/safeLock.ts (1)

18-22: beforeunload listener accumulates on repeated SafeLock() calls.

Each invocation of SafeLock() attaches a new beforeunload listener. Since getTokenLock() in Session.ts creates one lock per tokenId and caches it, this is somewhat mitigated. However, if SafeLock() were called repeatedly with different keys or without caching, listeners would accumulate.

Additionally, the async handler won't reliably complete before unload - beforeunload doesn't await promises.

Consider either:

  1. Documenting that callers must cache SafeLock instances (as done in Session.ts)
  2. Using a module-level cleanup pattern with a single listener managing all locks
packages/clerk-js/src/core/resources/Session.ts (1)

50-50: Consider adding bounds to the tokenLocks map.

The map grows indefinitely as new tokenId combinations are encountered. While typical usage is bounded, applications with many organizations or JWT templates could accumulate entries over extended sessions.

Consider adding an LRU-style eviction or clearing stale locks when they haven't been used recently:

const MAX_TOKEN_LOCKS = 50;

function getTokenLock(tokenId: string) {
  let lock = tokenLocks.get(tokenId);
  if (!lock) {
    // Simple eviction when limit reached
    if (tokenLocks.size >= MAX_TOKEN_LOCKS) {
      const firstKey = tokenLocks.keys().next().value;
      if (firstKey) tokenLocks.delete(firstKey);
    }
    lock = SafeLock(`clerk.lock.getToken.${tokenId}`);
    tokenLocks.set(tokenId, lock);
  }
  return lock;
}
packages/clerk-js/src/core/auth/__tests__/SessionCookiePoller.test.ts (1)

111-115: Consider a cleaner pattern to avoid non-null assertion.

The non-null assertion on resolveCallback! works but could be avoided.

Alternative pattern:

let resolveCallback: (() => void) | undefined;
const callbackPromise = new Promise<void>(resolve => {
  resolveCallback = resolve;
});
// Later:
resolveCallback?.();

Or use a deferred utility if available in the codebase.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 3303971 and 5ee4872.

📒 Files selected for processing (6)
  • .changeset/fix-token-refresh-race-condition.md (1 hunks)
  • packages/clerk-js/src/core/auth/AuthCookieService.ts (2 hunks)
  • packages/clerk-js/src/core/auth/SessionCookiePoller.ts (2 hunks)
  • packages/clerk-js/src/core/auth/__tests__/SessionCookiePoller.test.ts (1 hunks)
  • packages/clerk-js/src/core/auth/safeLock.ts (1 hunks)
  • packages/clerk-js/src/core/resources/Session.ts (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • .changeset/fix-token-refresh-race-condition.md
🧰 Additional context used
📓 Path-based instructions (9)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

All code must pass ESLint checks with the project's configuration

Files:

  • packages/clerk-js/src/core/resources/Session.ts
  • packages/clerk-js/src/core/auth/__tests__/SessionCookiePoller.test.ts
  • packages/clerk-js/src/core/auth/SessionCookiePoller.ts
  • packages/clerk-js/src/core/auth/AuthCookieService.ts
  • packages/clerk-js/src/core/auth/safeLock.ts
**/*.{js,jsx,ts,tsx,json,md,yml,yaml}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Use Prettier for consistent code formatting

Files:

  • packages/clerk-js/src/core/resources/Session.ts
  • packages/clerk-js/src/core/auth/__tests__/SessionCookiePoller.test.ts
  • packages/clerk-js/src/core/auth/SessionCookiePoller.ts
  • packages/clerk-js/src/core/auth/AuthCookieService.ts
  • packages/clerk-js/src/core/auth/safeLock.ts
packages/**/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

TypeScript is required for all packages

Files:

  • packages/clerk-js/src/core/resources/Session.ts
  • packages/clerk-js/src/core/auth/__tests__/SessionCookiePoller.test.ts
  • packages/clerk-js/src/core/auth/SessionCookiePoller.ts
  • packages/clerk-js/src/core/auth/AuthCookieService.ts
  • packages/clerk-js/src/core/auth/safeLock.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Follow established naming conventions (PascalCase for components, camelCase for variables)

Prefer importing types from @clerk/shared/types instead of the deprecated @clerk/types alias

Files:

  • packages/clerk-js/src/core/resources/Session.ts
  • packages/clerk-js/src/core/auth/__tests__/SessionCookiePoller.test.ts
  • packages/clerk-js/src/core/auth/SessionCookiePoller.ts
  • packages/clerk-js/src/core/auth/AuthCookieService.ts
  • packages/clerk-js/src/core/auth/safeLock.ts
packages/**/src/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

packages/**/src/**/*.{ts,tsx,js,jsx}: Maintain comprehensive JSDoc comments for public APIs
Use tree-shaking friendly exports
Validate all inputs and sanitize outputs
All public APIs must be documented with JSDoc
Use dynamic imports for optional features
Provide meaningful error messages to developers
Include error recovery suggestions where applicable
Log errors appropriately for debugging
Lazy load components and features when possible
Implement proper caching strategies
Use efficient data structures and algorithms
Implement proper logging with different levels

Files:

  • packages/clerk-js/src/core/resources/Session.ts
  • packages/clerk-js/src/core/auth/__tests__/SessionCookiePoller.test.ts
  • packages/clerk-js/src/core/auth/SessionCookiePoller.ts
  • packages/clerk-js/src/core/auth/AuthCookieService.ts
  • packages/clerk-js/src/core/auth/safeLock.ts
**/*.ts?(x)

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Use proper TypeScript error types

Files:

  • packages/clerk-js/src/core/resources/Session.ts
  • packages/clerk-js/src/core/auth/__tests__/SessionCookiePoller.test.ts
  • packages/clerk-js/src/core/auth/SessionCookiePoller.ts
  • packages/clerk-js/src/core/auth/AuthCookieService.ts
  • packages/clerk-js/src/core/auth/safeLock.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/typescript.mdc)

**/*.{ts,tsx}: Always define explicit return types for functions, especially public APIs
Use proper type annotations for variables and parameters where inference isn't clear
Avoid any type - prefer unknown when type is uncertain, then narrow with type guards
Implement type guards for unknown types using the pattern function isType(value: unknown): value is Type
Use interface for object shapes that might be extended
Use type for unions, primitives, and computed types
Prefer readonly properties for immutable data structures
Use private for internal implementation details in classes
Use protected for inheritance hierarchies
Use public explicitly for clarity in public APIs
Use mixins for shared behavior across unrelated classes in TypeScript
Use generic constraints with bounded type parameters like <T extends { id: string }>
Use utility types like Omit, Partial, and Pick for data transformation instead of manual type construction
Use discriminated unions instead of boolean flags for state management and API responses
Use mapped types for transforming object types
Use conditional types for type-level logic
Leverage template literal types for string manipulation at the type level
Use ES6 imports/exports consistently
Use default exports sparingly, prefer named exports
Document functions with JSDoc comments including @param, @returns, @throws, and @example tags
Create custom error classes that extend Error for specific error types
Use the Result pattern for error handling instead of throwing exceptions
Use optional chaining (?.) and nullish coalescing (??) operators for safe property access
Let TypeScript infer obvious types to reduce verbosity
Use const assertions with as const for literal types
Use satisfies operator for type checking without widening types
Declare readonly arrays and objects for immutable data structures
Use spread operator and array spread for immutable updates instead of mutations
Use lazy loading for large types...

Files:

  • packages/clerk-js/src/core/resources/Session.ts
  • packages/clerk-js/src/core/auth/__tests__/SessionCookiePoller.test.ts
  • packages/clerk-js/src/core/auth/SessionCookiePoller.ts
  • packages/clerk-js/src/core/auth/AuthCookieService.ts
  • packages/clerk-js/src/core/auth/safeLock.ts
**/*.{test,spec}.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

**/*.{test,spec}.{ts,tsx,js,jsx}: Unit tests are required for all new functionality
Verify proper error handling and edge cases
Include tests for all new features

Files:

  • packages/clerk-js/src/core/auth/__tests__/SessionCookiePoller.test.ts
**/*.{test,spec,e2e}.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Use real Clerk instances for integration tests

Files:

  • packages/clerk-js/src/core/auth/__tests__/SessionCookiePoller.test.ts
🧬 Code graph analysis (4)
packages/clerk-js/src/core/resources/Session.ts (7)
packages/clerk-js/src/core/auth/safeLock.ts (1)
  • SafeLock (15-62)
packages/clerk-js/src/core/tokenCache.ts (1)
  • SessionTokenCache (413-413)
packages/clerk-js/src/utils/debug.ts (1)
  • debugLogger (150-179)
packages/clerk-js/src/core/events.ts (2)
  • eventBus (32-32)
  • events (7-15)
packages/clerk-js/src/core/resources/Client.ts (1)
  • path (161-163)
packages/clerk-js/src/core/resources/User.ts (1)
  • path (111-113)
packages/clerk-js/src/core/resources/Token.ts (1)
  • Token (7-57)
packages/clerk-js/src/core/auth/__tests__/SessionCookiePoller.test.ts (1)
packages/clerk-js/src/core/auth/SessionCookiePoller.ts (1)
  • SessionCookiePoller (11-39)
packages/clerk-js/src/core/auth/AuthCookieService.ts (3)
packages/clerk-js/src/core/auth/cookies/clientUat.ts (2)
  • ClientUatCookieHandler (12-15)
  • createClientUatCookie (23-65)
packages/clerk-js/src/core/auth/SessionCookiePoller.ts (1)
  • SessionCookiePoller (11-39)
packages/clerk-js/src/core/auth/cookies/session.ts (2)
  • SessionCookieHandler (10-14)
  • createSessionCookie (28-59)
packages/clerk-js/src/core/auth/safeLock.ts (1)
packages/clerk-js/src/utils/debug.ts (1)
  • debugLogger (150-179)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (33)
  • GitHub Check: Integration Tests (expo-web, chrome)
  • GitHub Check: Integration Tests (vue, chrome)
  • GitHub Check: Integration Tests (machine, chrome)
  • GitHub Check: Integration Tests (nextjs, chrome, 15)
  • GitHub Check: Integration Tests (nextjs, chrome, 15, RQ)
  • GitHub Check: Integration Tests (nextjs, chrome, 14)
  • GitHub Check: Integration Tests (astro, chrome)
  • GitHub Check: Integration Tests (billing, chrome)
  • GitHub Check: Integration Tests (custom, chrome)
  • GitHub Check: Integration Tests (quickstart, chrome, 15)
  • GitHub Check: Integration Tests (react-router, chrome)
  • GitHub Check: Integration Tests (nextjs, chrome, 16)
  • GitHub Check: Integration Tests (tanstack-react-start, chrome)
  • GitHub Check: Integration Tests (quickstart, chrome, 16)
  • GitHub Check: Integration Tests (machine, chrome, RQ)
  • GitHub Check: Integration Tests (nuxt, chrome)
  • GitHub Check: Integration Tests (billing, chrome, RQ)
  • GitHub Check: Integration Tests (elements, chrome)
  • GitHub Check: Integration Tests (sessions:staging, chrome)
  • GitHub Check: Integration Tests (handshake, chrome)
  • GitHub Check: Integration Tests (generic, chrome)
  • GitHub Check: Integration Tests (ap-flows, chrome)
  • GitHub Check: Integration Tests (handshake:staging, chrome)
  • GitHub Check: Integration Tests (localhost, chrome)
  • GitHub Check: Integration Tests (sessions, chrome)
  • GitHub Check: Integration Tests (express, chrome)
  • GitHub Check: Publish with pkg-pr-new
  • GitHub Check: Static analysis
  • GitHub Check: Unit Tests (22, **)
  • GitHub Check: Unit Tests (22, shared, clerk-js, RQ)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: semgrep-cloud-platform/scan
  • GitHub Check: semgrep-cloud-platform/scan
🔇 Additional comments (6)
packages/clerk-js/src/core/resources/Session.ts (2)

45-64: LGTM - Well-implemented double-checked locking pattern.

The per-tokenId lock approach correctly allows different token types to be fetched in parallel while preventing duplicate fetches for the same token. The JSDoc comments clearly explain the design rationale.


441-444: Good pattern: Caching the promise prevents concurrent duplicate requests.

Caching tokenResolver before awaiting ensures that concurrent calls within the same tab will share the same in-flight request rather than triggering multiple API calls.

packages/clerk-js/src/core/auth/AuthCookieService.ts (1)

81-87: Initialization reordering looks correct.

The reordering of cookie handler creation (clientUatdevBrowsersessionCookie) doesn't introduce any dependency issues since these handlers are independent. The property order in the createDevBrowser call is a non-breaking change.

packages/clerk-js/src/core/auth/__tests__/SessionCookiePoller.test.ts (2)

1-14: Good test structure with proper setup and teardown.

The test suite correctly uses fake timers and restores them after each test. This ensures test isolation and deterministic behavior.


108-140: Excellent test for callback completion sequencing.

This test correctly validates that the next poll is only scheduled after the current callback completes, which is critical for preventing overlapping refresh operations.

packages/clerk-js/src/core/auth/SessionCookiePoller.ts (1)

5-10: Good documentation explaining the architectural decision.

The JSDoc clearly explains that cross-tab coordination is handled in Session.getToken(), making the poller's responsibility clear. This separation of concerns is a clean design.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
packages/clerk-js/src/core/auth/SessionCookiePoller.ts (2)

19-36: Add JSDoc documentation for public method.

The method lacks JSDoc documentation explaining its purpose, parameters, and behavior. As per coding guidelines, public APIs should be documented with comprehensive JSDoc comments.

Apply this diff:

+  /**
+   * Starts polling for session token refresh at regular intervals.
+   * @param cb - Async callback to execute for each refresh attempt
+   */
   public startPollingForSessionToken(cb: () => Promise<unknown>): void {

38-45: Add JSDoc documentation for public method.

The method lacks JSDoc documentation. As per coding guidelines, public APIs should be documented with comprehensive JSDoc comments.

Apply this diff:

+  /**
+   * Stops the session token polling.
+   */
   public stopPollingForSessionToken(): void {
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between bf4a9a3 and 80d9830.

📒 Files selected for processing (1)
  • packages/clerk-js/src/core/auth/SessionCookiePoller.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

All code must pass ESLint checks with the project's configuration

Files:

  • packages/clerk-js/src/core/auth/SessionCookiePoller.ts
**/*.{js,jsx,ts,tsx,json,md,yml,yaml}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Use Prettier for consistent code formatting

Files:

  • packages/clerk-js/src/core/auth/SessionCookiePoller.ts
packages/**/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

TypeScript is required for all packages

Files:

  • packages/clerk-js/src/core/auth/SessionCookiePoller.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Follow established naming conventions (PascalCase for components, camelCase for variables)

Prefer importing types from @clerk/shared/types instead of the deprecated @clerk/types alias

Files:

  • packages/clerk-js/src/core/auth/SessionCookiePoller.ts
packages/**/src/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

packages/**/src/**/*.{ts,tsx,js,jsx}: Maintain comprehensive JSDoc comments for public APIs
Use tree-shaking friendly exports
Validate all inputs and sanitize outputs
All public APIs must be documented with JSDoc
Use dynamic imports for optional features
Provide meaningful error messages to developers
Include error recovery suggestions where applicable
Log errors appropriately for debugging
Lazy load components and features when possible
Implement proper caching strategies
Use efficient data structures and algorithms
Implement proper logging with different levels

Files:

  • packages/clerk-js/src/core/auth/SessionCookiePoller.ts
**/*.ts?(x)

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Use proper TypeScript error types

Files:

  • packages/clerk-js/src/core/auth/SessionCookiePoller.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/typescript.mdc)

**/*.{ts,tsx}: Always define explicit return types for functions, especially public APIs
Use proper type annotations for variables and parameters where inference isn't clear
Avoid any type - prefer unknown when type is uncertain, then narrow with type guards
Implement type guards for unknown types using the pattern function isType(value: unknown): value is Type
Use interface for object shapes that might be extended
Use type for unions, primitives, and computed types
Prefer readonly properties for immutable data structures
Use private for internal implementation details in classes
Use protected for inheritance hierarchies
Use public explicitly for clarity in public APIs
Use mixins for shared behavior across unrelated classes in TypeScript
Use generic constraints with bounded type parameters like <T extends { id: string }>
Use utility types like Omit, Partial, and Pick for data transformation instead of manual type construction
Use discriminated unions instead of boolean flags for state management and API responses
Use mapped types for transforming object types
Use conditional types for type-level logic
Leverage template literal types for string manipulation at the type level
Use ES6 imports/exports consistently
Use default exports sparingly, prefer named exports
Document functions with JSDoc comments including @param, @returns, @throws, and @example tags
Create custom error classes that extend Error for specific error types
Use the Result pattern for error handling instead of throwing exceptions
Use optional chaining (?.) and nullish coalescing (??) operators for safe property access
Let TypeScript infer obvious types to reduce verbosity
Use const assertions with as const for literal types
Use satisfies operator for type checking without widening types
Declare readonly arrays and objects for immutable data structures
Use spread operator and array spread for immutable updates instead of mutations
Use lazy loading for large types...

Files:

  • packages/clerk-js/src/core/auth/SessionCookiePoller.ts
🧬 Code graph analysis (1)
packages/clerk-js/src/core/auth/SessionCookiePoller.ts (1)
packages/clerk-js/src/utils/debug.ts (1)
  • debugLogger (150-179)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Formatting | Dedupe | Changeset
  • GitHub Check: Build Packages
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: semgrep-cloud-platform/scan
  • GitHub Check: semgrep-cloud-platform/scan
🔇 Additional comments (3)
packages/clerk-js/src/core/auth/SessionCookiePoller.ts (3)

7-12: Good architectural documentation.

The JSDoc clearly explains that cross-tab coordination is now handled in Session.getToken() rather than within the poller itself, which aligns with the PR's approach to fixing the race condition.


26-32: Error handling correctly implemented.

The try/catch/finally block addresses the previous review concern about ensuring polling continues even if the callback fails. The finally block guarantees the timer is always rescheduled, and errors are properly logged for debugging.


38-45: Implementation is correct.

The method properly clears the timer and resets the initiated flag, allowing the poller to be restarted. The != null check correctly handles the case where timerId could be 0.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants